mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 04:46:40 +07:00
getting token / error
This commit is contained in:
parent
956802d5a5
commit
dc6f72baf1
26 changed files with 269 additions and 374 deletions
|
@ -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);
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
|
|
@ -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
18
src/redux/auth/actions.ts
Normal 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
15
src/redux/auth/api.ts
Normal 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)
|
||||||
|
);
|
|
@ -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,
|
36
src/redux/auth/handlers.ts
Normal file
36
src/redux/auth/handlers.ts
Normal 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
19
src/redux/auth/reducer.ts
Normal 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
33
src/redux/auth/sagas.ts
Normal 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;
|
4
src/redux/auth/selectors.ts
Normal file
4
src/redux/auth/selectors.ts
Normal 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;
|
14
src/redux/auth/transforms.ts
Normal file
14
src/redux/auth/transforms.ts
Normal 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
26
src/redux/auth/types.ts
Normal 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;
|
||||||
|
};
|
||||||
|
}>;
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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 });
|
|
|
@ -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) }))
|
|
||||||
);
|
|
|
@ -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);
|
|
|
@ -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;
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 } });
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue