-
- VAULT
-
+
depth
diff --git a/src/components/main/Header/style.scss b/src/components/main/Header/style.scss
index 19cb7471..e97570fa 100644
--- a/src/components/main/Header/style.scss
+++ b/src/components/main/Header/style.scss
@@ -7,11 +7,6 @@
height: 100px;
}
-.logo {
- font-size: 1.4em;
- font-weight: 800;
- display: flex;
-}
.spacer {
flex: 1;
@@ -36,7 +31,12 @@
display: block;
}
- &:last-child::after { display: none; }
+ &:last-child {
+ padding-right: 0;
+
+ &::after { display: none; }
+ }
+
}
}
diff --git a/src/components/main/Logo/index.tsx b/src/components/main/Logo/index.tsx
new file mode 100644
index 00000000..f1397106
--- /dev/null
+++ b/src/components/main/Logo/index.tsx
@@ -0,0 +1,8 @@
+import * as React from 'react';
+const style = require('./style.scss');
+
+export const Logo = () => (
+
+ VAULT
+
+);
diff --git a/src/components/main/Logo/style.scss b/src/components/main/Logo/style.scss
new file mode 100644
index 00000000..1f45b651
--- /dev/null
+++ b/src/components/main/Logo/style.scss
@@ -0,0 +1,5 @@
+.logo {
+ font-size: $text_sign;
+ font-weight: 800;
+ display: flex;
+}
diff --git a/src/constants/api.ts b/src/constants/api.ts
new file mode 100644
index 00000000..b0629e42
--- /dev/null
+++ b/src/constants/api.ts
@@ -0,0 +1,6 @@
+export const API = {
+ BASE: 'http://localhost:3000',
+ USER: {
+ LOGIN: '/user/login',
+ }
+};
diff --git a/src/containers/App.tsx b/src/containers/App.tsx
index 625c7c13..22fbf38b 100644
--- a/src/containers/App.tsx
+++ b/src/containers/App.tsx
@@ -2,12 +2,11 @@ import * as React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { hot } from 'react-hot-loader';
-import { SomeComponent } from '$components/SomeComponent';
import { ConnectedRouter } from "connected-react-router";
import { history } from "$redux/store";
import { NavLink, Switch, Route } from 'react-router-dom';
-import { MainLayout } from "$containers/MainLayout";
import { FlowLayout } from "$containers/FlowLayout";
+import { LoginLayout } from "$containers/LoginLayout";
interface IAppProps {}
interface IAppState {}
@@ -24,8 +23,8 @@ class Component extends React.Component
{
component={FlowLayout}
/>
diff --git a/src/containers/LoginLayout/index.tsx b/src/containers/LoginLayout/index.tsx
new file mode 100644
index 00000000..c2dec9a8
--- /dev/null
+++ b/src/containers/LoginLayout/index.tsx
@@ -0,0 +1,13 @@
+import * as React from 'react';
+import { LoginForm } from '$components/login/LoginForm';
+import { MainLayout } from "$containers/MainLayout";
+
+const style = require('./style.scss');
+
+export const LoginLayout: React.FunctionComponent<{}> = () => (
+
+
+
+
+
+);
diff --git a/src/containers/LoginLayout/style.scss b/src/containers/LoginLayout/style.scss
new file mode 100644
index 00000000..d7feb87f
--- /dev/null
+++ b/src/containers/LoginLayout/style.scss
@@ -0,0 +1,15 @@
+.container {
+
+}
+
+.form {
+ width: $content_width;
+ min-height: $cell * 2;
+ box-sizing: border-box;
+ border-radius: $panel_radius;
+ display: flex;
+ align-items: stretch;
+ justify-content: stretch;
+
+ //@include outer_shadow();
+}
diff --git a/src/redux/user/actions.ts b/src/redux/user/actions.ts
index 6f30ba12..0074719a 100644
--- a/src/redux/user/actions.ts
+++ b/src/redux/user/actions.ts
@@ -1,3 +1,16 @@
import { USER_ACTIONS } from "$redux/user/constants";
+import { IUserProfile } from "$redux/user/reducer";
-export const someAction = () => ({ type: USER_ACTIONS.SOME_ACTION });
+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
) => ({ type: USER_ACTIONS.SET_USER, profile });
diff --git a/src/redux/user/api.ts b/src/redux/user/api.ts
new file mode 100644
index 00000000..65e716dd
--- /dev/null
+++ b/src/redux/user/api.ts
@@ -0,0 +1,12 @@
+import { api } 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) }))
+);
diff --git a/src/redux/user/constants.ts b/src/redux/user/constants.ts
index aca439d2..a5aa84d2 100644
--- a/src/redux/user/constants.ts
+++ b/src/redux/user/constants.ts
@@ -1,3 +1,24 @@
export const USER_ACTIONS = {
- SOME_ACTION: 'SOME_ACTION',
+ SEND_LOGIN_REQUEST: 'SEND_LOGIN_REQUEST',
+ SET_LOGIN_ERROR: 'SET_LOGIN_ERROR',
+ SET_USER: 'SET_USER',
};
+
+export const USER_ERRORS = {
+ INVALID_CREDENTIALS: 'Неверное имя пользователя или пароль. Очень жаль.',
+ EMPTY_CREDENTIALS: 'Давайте введем логин и пароль. Это обязательно.'
+};
+
+export const USER_STATUSES = {
+ 404: USER_ERRORS.INVALID_CREDENTIALS,
+};
+
+export interface IApiUser {
+ id: number,
+ username: string,
+ email: string,
+ role: string,
+ activated: boolean,
+ createdAt: string,
+ updatedAt: string,
+}
diff --git a/src/redux/user/reducer.ts b/src/redux/user/reducer.ts
index 165a2a7a..8711b319 100644
--- a/src/redux/user/reducer.ts
+++ b/src/redux/user/reducer.ts
@@ -1,5 +1,5 @@
import { createReducer } from 'reduxsauce';
-import * as ACTIONS from "$redux/user/actions";
+import * as ActionCreators from "$redux/user/actions";
import { USER_ACTIONS } from "$redux/user/constants";
export interface IUserProfile {
@@ -8,13 +8,14 @@ export interface IUserProfile {
email: string,
role: string,
activated: boolean,
+ token: string,
}
export interface IUserFormStateLogin {
error: string,
}
-export type IRootState = Readonly<{
+export type IUserState = Readonly<{
profile: IUserProfile,
form_state: {
login: IUserFormStateLogin,
@@ -23,23 +24,40 @@ export type IRootState = Readonly<{
type UnsafeReturnType = T extends (...args: any[]) => infer R ? R : any;
interface ActionHandler {
- (state: IRootState, payload: UnsafeReturnType): IRootState;
+ (state: IUserState, payload: UnsafeReturnType): IUserState;
}
-const someActionHandler: ActionHandler = (state) => {
- return { ...state };
-};
+const setLoginErrorHandler: ActionHandler = (state, { error }) => ({
+ ...state,
+ form_state: {
+ ...state.form_state,
+ login: {
+ ...state.form_state.login,
+ error,
+ }
+ }
+});
+
+const setUserHandler: ActionHandler = (state, { profile }) => ({
+ ...state,
+ profile: {
+ ...state.profile,
+ ...profile,
+ }
+});
const HANDLERS = {
- [USER_ACTIONS.SOME_ACTION]: someActionHandler,
+ [USER_ACTIONS.SET_LOGIN_ERROR]: setLoginErrorHandler,
+ [USER_ACTIONS.SET_USER]: setUserHandler,
};
-const INITIAL_STATE: IRootState = {
+const INITIAL_STATE: IUserState = {
profile: {
id: 0,
username: '',
email: '',
role: '',
+ token: '',
activated: false,
},
form_state: {
diff --git a/src/redux/user/sagas.ts b/src/redux/user/sagas.ts
index 659d13ef..cc932012 100644
--- a/src/redux/user/sagas.ts
+++ b/src/redux/user/sagas.ts
@@ -1,25 +1,34 @@
-import { call, put, takeEvery, takeLatest } from 'redux-saga/effects';
-import { delay } from 'redux-saga';
-import { USER_ACTIONS } from "$redux/user/constants";
+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'
-// Worker Saga for SET_EDITOR_LOCATION_INPUT reducer
-/*
-function* fetchSuggestions({ payload }) {
- const { value } = payload;
+function* sendLoginRequestSaga({ username, password }: ReturnType): SagaIterator {
+ if (!username || !password) return yield put(userSetLoginError({ error: USER_ERRORS.EMPTY_CREDENTIALS }));
- yield delay(300);
- try {
- const results = yield call(someFunction, arguments);
- yield put({ type: TYPES.ANOTHER_ACTION, payload: { results } });
- } catch (e) {
- yield put({ type: TYPES.ANOTHER_ACTION, payload: { results } });
- }
+ 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 } = user;
+
+ yield put(userSetUser({
+ token,
+ id,
+ role,
+ email,
+ username: user.username,
+ activated,
+ }));
+
+ yield put(push('/'));
}
-*/
function* mySaga() {
- // fetch autocompletion on location input
- //yield takeLatest(TYPES.ACTION, function);
+ yield takeLatest(USER_ACTIONS.SEND_LOGIN_REQUEST, sendLoginRequestSaga);
}
export default mySaga;
diff --git a/src/styles/colors.scss b/src/styles/colors.scss
index b614e702..af62e1f4 100644
--- a/src/styles/colors.scss
+++ b/src/styles/colors.scss
@@ -1,6 +1,21 @@
+$color_red: #ff3344;
+$color_yellow: #ffd60f;
+$color_blue: complement($color_red);
+//$color_yellow: complement($color_red);
+//$color_yellow: yellow;
+
$main_bg_color: #161616;
$main_text_color: white;
$content_bg_color: #222222;
+$content_bg_secondary: darken($content_bg_color, 3%);
$cell_bg: transparentize(white, 0.95);
+
+$text_normal: 16px;
+$text_small: 14px;
+$text_big: 20px;
+$text_sign: 22px;
+
+$input_bg_color: transparentize(black, 0.8);
+$button_bg_color: #ff3344;
diff --git a/src/styles/global.scss b/src/styles/global.scss
index 41554b46..45427fd0 100644
--- a/src/styles/global.scss
+++ b/src/styles/global.scss
@@ -25,6 +25,18 @@ body {
}
}
+:global(.gap) {
+ height: $gap;
+}
+
+:global(.spc) {
+ height: $spc;
+
+ &:global(.double) { height: $spc * 2; }
+ &:global(.quadro) { height: $spc * 4; }
+ &:global(.sixty) { height: $spc * 6; }
+}
+
:global(.padded) {
padding: $gap;
}
@@ -46,3 +58,4 @@ body {
:global(.footer) {
height: 40px;
}
+
diff --git a/src/styles/variables.scss b/src/styles/variables.scss
index 53e1dd3f..18de67fb 100644
--- a/src/styles/variables.scss
+++ b/src/styles/variables.scss
@@ -6,6 +6,33 @@ $gap: 8px;
$spc: $gap * 2;
$panel_radius: 1px;
+$grid_line: 4px;
+
+$input_height: 32px;
+$input_radius: 2px;
+
+$info_height: 24px;
+
@mixin outer_shadow() {
- box-shadow: transparentize(white, 0.92) -1px -1px, transparentize(black, 0.8) 1px 1px;
+ box-shadow: inset transparentize(white, 0.95) 0 1px,
+ inset transparentize(black, 0.5) 0 -1px;
+}
+
+@mixin inner_shadow() {
+ box-shadow: inset transparentize(white, 0.95) 0 -1px,
+ inset transparentize(black, 0.5) 0 1px;
+}
+
+@mixin input_shadow() {
+ box-shadow: inset transparentize(white, 0.92) 0 -1px,
+ inset transparentize(black, 0.8) 0 1px;
+}
+
+@mixin modal_mixin() {
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
}
diff --git a/src/utils/api/index.ts b/src/utils/api/index.ts
new file mode 100644
index 00000000..4cf829df
--- /dev/null
+++ b/src/utils/api/index.ts
@@ -0,0 +1,6 @@
+import axios from 'axios';
+import { API } from "$constants/api";
+
+export const api = axios.create({
+ baseURL: API.BASE,
+});