1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-24 20:36:40 +07:00

getting token / error

This commit is contained in:
muerwre 2019-08-03 12:12:23 +07:00
parent 956802d5a5
commit dc6f72baf1
26 changed files with 269 additions and 374 deletions

View file

@ -1,103 +0,0 @@
import * as React from 'react';
import { TextInput } from "~/components/input/TextInput";
import { Button } from "~/components/input/Button";
import { connect } from 'react-redux';
import { bindActionCreators } from "redux";
import { userSendLoginRequest, userSetLoginError } from "~/redux/user/actions";
import { IUserFormStateLogin, IUserState } from "~/redux/user/reducer";
import { Info } from "~/components/input/Info";
import * as login from '~/containers/login/LoginLayout/styles.scss';
import * as style from './style.scss';
interface ILoginFormProps {
error: IUserFormStateLogin['error'],
userSendLoginRequest: typeof userSendLoginRequest,
userSetLoginError: typeof userSetLoginError,
}
interface ILoginFormState {
username: string,
password: string,
}
class Component extends React.PureComponent<ILoginFormProps, ILoginFormState> {
state = {
username: 'user',
password: 'password',
};
sendRequest = () => {
console.log('send?');
this.props.userSendLoginRequest(this.state);
};
changeField = <T extends keyof ILoginFormState>(field: T) => ({ target: { value }}: React.ChangeEvent<HTMLInputElement>) => {
if (this.props.error) this.props.userSetLoginError({ error: null });
this.setState({ [field]: value } as Pick<ILoginFormState, keyof ILoginFormState>);
};
render() {
const { error } = this.props;
const { username, password } = this.state;
return (
<div className={login.form}>
<div className={style.container}>
<div className={style.area_left}>
</div>
<div className={style.area_right}>
<div className={style.area_sign}>
РЕШИТЕЛЬНО<br />ВОЙТИ
</div>
<div className="spc double" />
<div className={style.inputs}>
<TextInput
label="Логин"
value={username}
onChange={this.changeField('username')}
/>
<div className="gap" />
<TextInput
label="Пароль"
type="password"
value={password}
onChange={this.changeField('password')}
/>
<div className="spc double" />
<Button onClick={this.sendRequest}>
Войти
</Button>
{
error &&
<React.Fragment>
<div className="spc" />
<Info>
{error}
</Info>
</React.Fragment>
}
</div>
</div>
</div>
</div>
)
}
}
const mapStateToProps = ({ user: { form_state: { login }}}: { user: IUserState }) => ({ ...login });
const mapDispatchToProps = dispatch => bindActionCreators({
userSendLoginRequest,
userSetLoginError,
}, dispatch);
export const LoginForm = connect(mapStateToProps, mapDispatchToProps)(Component);

View file

@ -1,44 +0,0 @@
.container {
display: grid;
flex: 1;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: 1fr;
grid-row-gap: $grid_line;
grid-column-gap: $grid_line;
}
.area_left {
grid-column-end: span 3;
//background: $content_bg_color;
padding: $spc;
border-radius: $panel_radius 0 0 $panel_radius;
display: flex;
align-items: center;
justify-content: center;
//background: url('../../../sprites/splotchy.svg');
// background-size: cover;
//@include outer_shadow();
}
.area_right {
grid-column-end: span 1;
background: $content_bg_secondary;
padding: $spc;
border-radius: $panel_radius 0 0 $panel_radius;
user-select: none;
@include outer_shadow();
}
.area_sign {
font-size: $text_sign;
font-weight: 800;
line-height: 1.2em;
}
.inputs {
display: flex;
align-items: stretch;
flex-direction: column;
}

View file

@ -1,29 +1,23 @@
import * as React from "react"; import * as React from "react";
import { Logo } from "~/components/main/Logo"; import { Logo } from "~/components/main/Logo";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { IUserState } from "~/redux/user/reducer";
import { push as historyPush } from "connected-react-router"; import { push as historyPush } from "connected-react-router";
import * as style from "./style.scss"; import * as style from "./style.scss";
import { Filler } from "~/components/containers/Filler"; import { Filler } from "~/components/containers/Filler";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import {selectUser} from "~/redux/auth/selectors";
import {Group} from "~/components/containers/Group";
const mapStateToProps = ({ const mapStateToProps = selectUser;
user: {
profile: { username, is_user }
}
}: {
user: IUserState;
}) => ({ username, is_user });
const mapDispatchToProps = { const mapDispatchToProps = {
push: historyPush push: historyPush
}; };
type IHeaderProps = ReturnType<typeof mapStateToProps> & type IProps = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
typeof mapDispatchToProps & {};
export const Component: React.FunctionComponent<IHeaderProps> = ({ const HeaderUnconnected: React.FunctionComponent<IProps> = ({
username, username,
is_user is_user
}) => { }) => {
@ -43,16 +37,18 @@ export const Component: React.FunctionComponent<IHeaderProps> = ({
<Filler /> <Filler />
{/* <Group horizontal className={style.user_button}> <Group horizontal className={style.user_button}>
<div>username</div> <div>username</div>
<div className={style.user_avatar}/> <div className={style.user_avatar}/>
</Group> */} </Group>
</div> </div>
</div> </div>
); );
}; };
export const Header = connect( const Header = connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(Component); )(HeaderUnconnected);
export { Header };

View file

@ -1,6 +1,6 @@
export const API = { export const API = {
BASE: 'http://localhost:3000', BASE: 'http://localhost:3333',
USER: { USER: {
LOGIN: '/user/login', LOGIN: '/auth/login',
} }
}; };

View file

@ -6,7 +6,6 @@ import { ConnectedRouter } from "connected-react-router";
import { history } from "~/redux/store"; import { history } from "~/redux/store";
import { NavLink, Switch, Route, Redirect } from "react-router-dom"; import { NavLink, Switch, Route, Redirect } from "react-router-dom";
import { FlowLayout } from "~/containers/flow/FlowLayout"; import { FlowLayout } from "~/containers/flow/FlowLayout";
import { LoginLayout } from "~/containers/login/LoginLayout";
import { MainLayout } from "~/containers/main/MainLayout"; import { MainLayout } from "~/containers/main/MainLayout";
import { ImageExample } from "~/containers/examples/ImageExample"; import { ImageExample } from "~/containers/examples/ImageExample";
import { EditorExample } from "~/containers/examples/EditorExample"; import { EditorExample } from "~/containers/examples/EditorExample";
@ -15,7 +14,7 @@ import { Sprites } from "~/sprites/Sprites";
import { URLS } from "~/constants/urls"; import { URLS } from "~/constants/urls";
import { Modal } from "~/containers/dialogs/Modal"; import { Modal } from "~/containers/dialogs/Modal";
import { selectModal } from "~/redux/modal/selectors"; import { selectModal } from "~/redux/modal/selectors";
import { BlurWrapper } from "../components/containers/BlurWrapper/index"; import { BlurWrapper } from "~/components/containers/BlurWrapper";
const mapStateToProps = selectModal; const mapStateToProps = selectModal;
const mapDispatchToProps = {}; const mapDispatchToProps = {};
@ -37,8 +36,6 @@ class Component extends React.Component<IProps, {}> {
<Route path="/examples/horizontal" component={HorizontalExample} /> <Route path="/examples/horizontal" component={HorizontalExample} />
<Route exact path={URLS.BASE} component={FlowLayout} /> <Route exact path={URLS.BASE} component={FlowLayout} />
<Route path={URLS.AUTH.LOGIN} component={LoginLayout} />
<Redirect to="/" /> <Redirect to="/" />
</Switch> </Switch>
</MainLayout> </MainLayout>

View file

@ -1,4 +1,4 @@
import React, { FC, useState } from 'react'; import React, {FC, FormEvent, useCallback, useEffect, useState} from 'react';
import { ScrollDialog } from '../ScrollDialog'; import { ScrollDialog } from '../ScrollDialog';
import { IDialogProps } from '~/redux/modal/constants'; import { IDialogProps } from '~/redux/modal/constants';
import { useCloseOnEscape } from '~/utils/hooks'; import { useCloseOnEscape } from '~/utils/hooks';
@ -7,12 +7,32 @@ import { InputText } from '~/components/input/InputText';
import { Button } from '~/components/input/Button'; import { Button } from '~/components/input/Button';
import { Padder } from '~/components/containers/Padder'; import { Padder } from '~/components/containers/Padder';
import * as styles from './styles.scss'; import * as styles from './styles.scss';
type IProps = IDialogProps & {}; import {selectAuthLogin} from "~/redux/auth/selectors";
import * as ACTIONS from '~/redux/auth/actions';
import {connect} from "react-redux";
const LoginDialog: FC<IProps> = ({ onRequestClose }) => { const mapStateToProps = selectAuthLogin;
const mapDispatchToProps = {
userSendLoginRequest: ACTIONS.userSendLoginRequest,
userSetLoginError: ACTIONS.userSetLoginError,
};
type IProps = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & IDialogProps & {};
const LoginDialogUnconnected: FC<IProps> = ({ onRequestClose, error , userSendLoginRequest, userSetLoginError }) => {
const [username, setUserName] = useState(''); const [username, setUserName] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const onSubmit = useCallback((event: FormEvent) => {
event.preventDefault();
userSendLoginRequest({ username, password });
}, [userSendLoginRequest, username, password]);
useEffect(() => {
if (error) userSetLoginError(null);
}, [username, password]);
const buttons = ( const buttons = (
<Padder> <Padder>
<Group horizontal> <Group horizontal>
@ -23,23 +43,29 @@ const LoginDialog: FC<IProps> = ({ onRequestClose }) => {
useCloseOnEscape(onRequestClose); useCloseOnEscape(onRequestClose);
console.log({ error });
return ( return (
<ScrollDialog buttons={buttons} width={260}> <form onSubmit={onSubmit}>
<Padder> <ScrollDialog buttons={buttons} width={260}>
<div className={styles.wrap}> <Padder>
<Group> <div className={styles.wrap}>
<h2>РЕШИТЕЛЬНО ВОЙТИ</h2> <Group>
<h2>РЕШИТЕЛЬНО ВОЙТИ</h2>
<div /> <div />
<div /> <div />
<InputText title="Логин" handler={setUserName} value={username} /> <InputText title="Логин" handler={setUserName} value={username} error={error} />
<InputText title="Пароль" handler={setPassword} value={password} /> <InputText title="Пароль" handler={setPassword} value={password} />
</Group> </Group>
</div> </div>
</Padder> </Padder>
</ScrollDialog> </ScrollDialog>
</form>
); );
}; };
const LoginDialog = connect(mapStateToProps, mapDispatchToProps)(LoginDialogUnconnected);
export { LoginDialog }; export { LoginDialog };

View file

@ -1,18 +0,0 @@
import * as React from 'react';
import { LoginForm } from '~/components/login/LoginForm';
import { Header } from "~/components/main/Header";
import { GodRays } from "~/components/main/GodRays";
import * as styles from './styles.scss';
export const LoginLayout: React.FunctionComponent<{}> = () => (
<div className={styles.wrapper}>
<GodRays />
<div className={styles.header}>
<Header />
</div>
<div className={styles.container}>
<LoginForm />
</div>
</div>
);

View file

@ -1,30 +0,0 @@
.wrapper {
min-height: 100vh;
display: flex;
justify-content: center;
flex-direction: column;
align-items: stretch;
}
.header {}
.container {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
justify-self: stretch;
}
.form {
width: $content_width;
min-height: $cell * 2;
box-sizing: border-box;
border-radius: $panel_radius;
display: flex;
align-items: stretch;
justify-content: stretch;
// background: rgba(0,0,0,0.1);
// @include outer_shadow();
}

18
src/redux/auth/actions.ts Normal file
View file

@ -0,0 +1,18 @@
import { AUTH_USER_ACTIONS } from "~/redux/auth/constants";
import {IAuthState, IUser} from "~/redux/auth/types";
export const userSendLoginRequest = ({
username, password
}: {
username: string, password: string
}) => ({ type: AUTH_USER_ACTIONS.SEND_LOGIN_REQUEST, username, password });
export const userSetLoginError = (error: IAuthState['login']['error']) => ({
type: AUTH_USER_ACTIONS.SET_LOGIN_ERROR, error
});
export const authSetToken = (token: IAuthState['token']) => ({
type: AUTH_USER_ACTIONS.SET_TOKEN, token,
});
export const authSetUser = (profile: Partial<IUser>) => ({ type: AUTH_USER_ACTIONS.SET_USER, profile });

15
src/redux/auth/api.ts Normal file
View file

@ -0,0 +1,15 @@
import {api, authMiddleware, errorMiddleware, resultMiddleware} from "~/utils/api";
import { API } from "~/constants/api";
import { IApiUser } from "~/redux/auth/constants";
import {IResultWithStatus} from "~/redux/types";
import {userLoginTransform} from "~/redux/auth/transforms";
export const apiUserLogin = (
{ username, password }:
{ username: string, password: string }
): Promise<IResultWithStatus<{ token: string, status?: number, user?: IApiUser }>> => (
api.post(API.USER.LOGIN, { username, password })
.then(resultMiddleware)
.catch(errorMiddleware)
.then(userLoginTransform)
);

View file

@ -1,7 +1,10 @@
export const USER_ACTIONS = { import {IToken, IUser} from "~/redux/auth/types";
export const AUTH_USER_ACTIONS = {
SEND_LOGIN_REQUEST: 'SEND_LOGIN_REQUEST', SEND_LOGIN_REQUEST: 'SEND_LOGIN_REQUEST',
SET_LOGIN_ERROR: 'SET_LOGIN_ERROR', SET_LOGIN_ERROR: 'SET_LOGIN_ERROR',
SET_USER: 'SET_USER', SET_USER: 'SET_USER',
SET_TOKEN: 'SET_TOKEN',
}; };
export const USER_ERRORS = { export const USER_ERRORS = {
@ -13,6 +16,28 @@ export const USER_STATUSES = {
404: USER_ERRORS.INVALID_CREDENTIALS, 404: USER_ERRORS.INVALID_CREDENTIALS,
}; };
export const USER_ROLES = {
GUEST: 'guest',
USER: 'user',
ADMIN: 'admin',
};
export const EMPTY_TOKEN: IToken = {
access: null,
refresh: null,
};
export const EMPTY_USER: IUser = {
id: null,
role: USER_ROLES.GUEST,
email: null,
name: null,
username: null,
photo: null,
is_activated: false,
is_user: false,
};
export interface IApiUser { export interface IApiUser {
id: number, id: number,
username: string, username: string,

View file

@ -0,0 +1,36 @@
import {AUTH_USER_ACTIONS} from "~/redux/auth/constants";
import * as ActionCreators from "~/redux/auth/actions";
import {IAuthState} from "~/redux/auth/types";
interface ActionHandler<T> {
(state: IAuthState, payload: T extends (...args: any[]) => infer R ? R : any): IAuthState;
}
const setLoginError: ActionHandler<typeof ActionCreators.userSetLoginError> = (
state,
{ error }
) => ({
...state,
login: {
...state.login,
error,
}
});
const setUser: ActionHandler<typeof ActionCreators.authSetUser> = (state, { profile }) => ({
...state,
user: {
...state.user,
...profile
}
});
const setToken: ActionHandler<typeof ActionCreators.authSetToken> = (state, { token }) => ({
...state, token,
});
export const AUTH_USER_HANDLERS = {
[AUTH_USER_ACTIONS.SET_LOGIN_ERROR]: setLoginError,
[AUTH_USER_ACTIONS.SET_USER]: setUser,
[AUTH_USER_ACTIONS.SET_TOKEN]: setToken,
};

19
src/redux/auth/reducer.ts Normal file
View file

@ -0,0 +1,19 @@
import {EMPTY_TOKEN, EMPTY_USER, AUTH_USER_ACTIONS} from "~/redux/auth/constants";
import { createReducer } from "~/utils/reducer";
import {IAuthState} from "~/redux/auth/types";
import {AUTH_USER_HANDLERS} from "~/redux/auth/handlers";
const HANDLERS = {
...AUTH_USER_HANDLERS,
};
const INITIAL_STATE: IAuthState = {
token: { ...EMPTY_TOKEN },
user: { ...EMPTY_USER },
login: {
error: null,
is_loading: false,
},
};
export default createReducer(INITIAL_STATE, HANDLERS);

33
src/redux/auth/sagas.ts Normal file
View file

@ -0,0 +1,33 @@
import {call, put, takeLatest } from 'redux-saga/effects';
import { SagaIterator } from 'redux-saga';
import {AUTH_USER_ACTIONS} from "~/redux/auth/constants";
import * as ActionCreators from '~/redux/auth/actions';
import {authSetToken, userSetLoginError} from "~/redux/auth/actions";
import {apiUserLogin} from "~/redux/auth/api";
function* sendLoginRequestSaga({ username, password }: ReturnType<typeof ActionCreators.userSendLoginRequest>): SagaIterator {
if (!username || !password) return;
const { error, data: { access, refresh, user }} = yield call(apiUserLogin, { username, password });
console.log({ access, refresh, user, error });
if (error) return yield put(userSetLoginError(error));
yield put(authSetToken({ access, refresh }));
// const { token, status, user }:
// { token: string, status: number, user: IApiUser } = yield call(apiUserLogin, { username, password });
//
// if (!token) return yield put(userSetLoginError({ error: USER_STATUSES[status] || USER_ERRORS.INVALID_CREDENTIALS }));
//
// const { id, role, email, activated: is_activated } = user;
//
// yield put(userSetUser({ token, id, role, email, username: user.username, is_activated, is_user: true }));
// yield put(push('/'));
}
function* mySaga() {
yield takeLatest(AUTH_USER_ACTIONS.SEND_LOGIN_REQUEST, sendLoginRequestSaga);
}
export default mySaga;

View file

@ -0,0 +1,4 @@
import {IState} from "~/redux/store";
export const selectUser = (state: IState): IState['auth']['user'] => state.auth.user;
export const selectAuthLogin = (state: IState): IState['auth']['login'] => state.auth.login;

View file

@ -0,0 +1,14 @@
import {IResultWithStatus} from "~/redux/types";
export const userLoginTransform = ({ status, data,error }: IResultWithStatus<any>): IResultWithStatus<any> => {
switch(true) {
case status === 401 || !data.access || data.refresh:
return { status, data, error: 'Пользователь не найден' };
case status === 200:
return { status, data, error: null };
default:
return { status, data, error: error || 'Неизвестная ошибка' };
}
}

26
src/redux/auth/types.ts Normal file
View file

@ -0,0 +1,26 @@
export interface IToken {
access: string;
refresh: string;
}
export interface IUser {
id: number;
username: string;
email: string;
role: string;
photo: string;
name: string;
is_activated: boolean;
is_user: boolean;
}
export type IAuthState = Readonly<{
user: IUser;
token: IToken;
login: {
error: string;
is_loading: boolean;
};
}>;

View file

@ -10,7 +10,7 @@ export interface IModalState {
const INITIAL_STATE: IModalState = { const INITIAL_STATE: IModalState = {
is_shown: true, is_shown: true,
dialog: DIALOGS.TEST, dialog: DIALOGS.LOGIN,
}; };
export default createReducer(INITIAL_STATE, MODAL_HANDLERS); export default createReducer(INITIAL_STATE, MODAL_HANDLERS);

View file

@ -8,8 +8,10 @@ import { createBrowserHistory } from "history";
import { PersistConfig, Persistor } from "redux-persist/es/types"; import { PersistConfig, Persistor } from "redux-persist/es/types";
import { routerMiddleware } from "connected-react-router"; import { routerMiddleware } from "connected-react-router";
import userReducer, { IUserState } from "~/redux/user/reducer"; import userReducer from "~/redux/auth/reducer";
import userSaga from "~/redux/user/sagas"; import userSaga from "~/redux/auth/sagas";
import { IAuthState } from "~/redux/auth/types";
import modalReducer, { IModalState } from "~/redux/modal/reducer"; import modalReducer, { IModalState } from "~/redux/modal/reducer";
import { IState } from "~/redux/store"; import { IState } from "~/redux/store";
@ -21,7 +23,7 @@ const userPersistConfig: PersistConfig = {
}; };
export interface IState { export interface IState {
user: IUserState; auth: IAuthState;
modal: IModalState; modal: IModalState;
router: RouterState; router: RouterState;
} }
@ -35,15 +37,15 @@ const composeEnhancers =
: compose; : compose;
export const store = createStore( export const store = createStore(
combineReducers({ combineReducers<IState>({
user: persistReducer(userPersistConfig, userReducer), auth: persistReducer(userPersistConfig, userReducer),
modal: modalReducer, modal: modalReducer,
router: connectRouter(history) router: connectRouter(history)
}), }),
composeEnhancers(applyMiddleware(routerMiddleware(history), sagaMiddleware)) composeEnhancers(applyMiddleware(routerMiddleware(history), sagaMiddleware))
); );
export function configureStore(): { store: Store<any>; persistor: Persistor } { export function configureStore(): { store: Store<IState>; persistor: Persistor } {
sagaMiddleware.run(userSaga); sagaMiddleware.run(userSaga);
const persistor = persistStore(store); const persistor = persistStore(store);

View file

@ -31,3 +31,9 @@ export interface IDialogProps {
onRequestClose: () => void; onRequestClose: () => void;
onDialogChange: (dialog: ValueOf<typeof DIALOGS>) => void; onDialogChange: (dialog: ValueOf<typeof DIALOGS>) => void;
} }
export interface IResultWithStatus<T> {
status: number;
data: T;
error?: string;
}

View file

@ -1,16 +0,0 @@
import { USER_ACTIONS } from "~/redux/user/constants";
import { IUserProfile } from "~/redux/user/reducer";
export const userSendLoginRequest = ({
username, password
}: {
username: string, password: string
}) => ({ type: USER_ACTIONS.SEND_LOGIN_REQUEST, username, password });
export const userSetLoginError = ({
error
}: {
error: string
}) => ({ type: USER_ACTIONS.SET_LOGIN_ERROR, error });
export const userSetUser = (profile: Partial<IUserProfile>) => ({ type: USER_ACTIONS.SET_USER, profile });

View file

@ -1,12 +0,0 @@
import { api, authMiddleware } from "~/utils/api";
import { API } from "~/constants/api";
import { IApiUser } from "~/redux/user/constants";
export const apiUserLogin = (
{ username, password }:
{ username: string, password: string }
): Promise<{ token: string, status?: number, user?: IApiUser }> => (
api.post(API.USER.LOGIN, { username, password })
.then(r => r && r.data && { token: r.data.token, user: r.data.user, status: 200 })
.catch( (r) => ({ token: '', user: null, status: parseInt(r.response.status) }))
);

View file

@ -1,76 +0,0 @@
import * as ActionCreators from "~/redux/user/actions";
import { USER_ACTIONS } from "~/redux/user/constants";
import { createReducer } from "~/utils/reducer";
export interface IUserProfile {
id: number;
username: string;
email: string;
role: string;
token: string;
is_activated: boolean;
is_user: boolean;
}
export interface IUserFormStateLogin {
error: string;
}
export type IUserState = Readonly<{
profile: IUserProfile;
form_state: {
login: IUserFormStateLogin;
};
}>;
type UnsafeReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
interface ActionHandler<T> {
(state: IUserState, payload: UnsafeReturnType<T>): IUserState;
}
const setLoginErrorHandler: ActionHandler<typeof ActionCreators.userSetLoginError> = (
state,
{ error }
) => ({
...state,
form_state: {
...state.form_state,
login: {
...state.form_state.login,
error
}
}
});
const setUserHandler: ActionHandler<typeof ActionCreators.userSetUser> = (state, { profile }) => ({
...state,
profile: {
...state.profile,
...profile
}
});
const HANDLERS = {
[USER_ACTIONS.SET_LOGIN_ERROR]: setLoginErrorHandler,
[USER_ACTIONS.SET_USER]: setUserHandler
};
const INITIAL_STATE: IUserState = {
profile: {
id: 0,
username: "",
email: "",
role: "",
token: "",
is_activated: false,
is_user: false
},
form_state: {
login: {
error: ""
}
}
};
export default createReducer(INITIAL_STATE, HANDLERS);

View file

@ -1,27 +0,0 @@
import { call, put, takeLatest } from 'redux-saga/effects';
import { SagaIterator } from 'redux-saga';
import { IApiUser, USER_ACTIONS, USER_ERRORS, USER_STATUSES } from "~/redux/user/constants";
import * as ActionCreators from '~/redux/user/actions';
import { apiUserLogin } from "~/redux/user/api";
import { userSetLoginError, userSetUser } from "~/redux/user/actions";
import { push } from 'connected-react-router'
function* sendLoginRequestSaga({ username, password }: ReturnType<typeof ActionCreators.userSendLoginRequest>): SagaIterator {
if (!username || !password) return yield put(userSetLoginError({ error: USER_ERRORS.EMPTY_CREDENTIALS }));
const { token, status, user }:
{ token: string, status: number, user: IApiUser } = yield call(apiUserLogin, { username, password });
if (!token) return yield put(userSetLoginError({ error: USER_STATUSES[status] || USER_ERRORS.INVALID_CREDENTIALS }));
const { id, role, email, activated: is_activated } = user;
yield put(userSetUser({ token, id, role, email, username: user.username, is_activated, is_user: true }));
yield put(push('/'));
}
function* mySaga() {
yield takeLatest(USER_ACTIONS.SEND_LOGIN_REQUEST, sendLoginRequestSaga);
}
export default mySaga;

View file

@ -18,7 +18,7 @@
position: absolute; position: absolute;
width: $gap * 2; width: $gap * 2;
height: $input_height; height: $input_height;
top: 0; top: 1px;
right: 1px; right: 1px;
transform: translateX(0); transform: translateX(0);
transition: transform 0.25s; transition: transform 0.25s;
@ -282,11 +282,12 @@
color: $red; color: $red;
span { span {
background: white; background: $content_bg;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
padding: 0 5px; padding: 0 2px;
border-radius: $radius;
} }
} }

View file

@ -11,3 +11,6 @@ export const authMiddleware = r => {
export const api = axios.create({ export const api = axios.create({
baseURL: API.BASE, baseURL: API.BASE,
}); });
export const resultMiddleware = ({ status, data }) => ({ status, data });
export const errorMiddleware = ({ status, data, response }) => ({ status, data: data || { response } });