diff --git a/cvat-ui/package.json b/cvat-ui/package.json
index 482e32d3..e198d615 100644
--- a/cvat-ui/package.json
+++ b/cvat-ui/package.json
@@ -10,6 +10,7 @@
"@types/react-dom": "16.8.4",
"@types/react-redux": "^7.1.1",
"@types/react-router-dom": "^4.3.4",
+ "@types/redux-logger": "^3.0.7",
"antd": "^3.19.1",
"babel-plugin-import": "^1.11.2",
"customize-cra": "^0.2.12",
@@ -24,6 +25,7 @@
"react-router-dom": "^5.0.1",
"react-scripts": "3.0.1",
"redux": "^4.0.3",
+ "redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0",
"source-map-explorer": "^1.8.0",
"typescript": "3.4.5"
diff --git a/cvat-ui/src/actions/auth.actions.ts b/cvat-ui/src/actions/auth.actions.ts
index 864c4d4b..64fe491d 100644
--- a/cvat-ui/src/actions/auth.actions.ts
+++ b/cvat-ui/src/actions/auth.actions.ts
@@ -17,13 +17,15 @@ export const loginError = (error = {}) => (dispatch: any) => {
});
}
-export const loginAsync = (username: string, password: string) => {
+export const loginAsync = (username: string, password: string, history: any) => {
return (dispatch: any) => {
dispatch(login());
return (window as any).cvat.server.login(username, password).then(
(authenticated: any) => {
+ localStorage.setItem('session', 'true');
dispatch(loginSuccess());
+ history.push(history.location.state ? history.location.state.from : '/dashboard');
},
(error: any) => {
dispatch(loginError(error));
diff --git a/cvat-ui/src/actions/tasks.actions.ts b/cvat-ui/src/actions/tasks.actions.ts
index 481176b4..91a072ae 100644
--- a/cvat-ui/src/actions/tasks.actions.ts
+++ b/cvat-ui/src/actions/tasks.actions.ts
@@ -1,3 +1,8 @@
+import queryString from 'query-string';
+
+import setQueryObject from '../utils/tasks-filter-dto'
+
+
export const getTasks = () => (dispatch: any, getState: any) => {
dispatch({
type: 'GET_TASKS',
@@ -52,14 +57,31 @@ export const getTasksAsync = (queryObject = {}) => {
};
}
-export const deleteTaskAsync = (task: any) => {
- return (dispatch: any) => {
+export const deleteTaskAsync = (task: any, history: any) => {
+ return (dispatch: any, getState: any) => {
dispatch(deleteTask());
return task.delete().then(
(deleted: any) => {
dispatch(deleteTaskSuccess());
- dispatch(getTasksAsync());
+
+ const state = getState();
+
+ const queryObject = {
+ page: state.tasksFilter.currentPage,
+ search: state.tasksFilter.searchQuery,
+ }
+
+ if (state.tasks.tasks.length === 1 && state.tasks.tasksCount !== 1) {
+ queryObject.page = queryObject.page - 1;
+
+ history.push({ search: queryString.stringify(queryObject) });
+ } else if (state.tasks.tasksCount === 1) {
+ dispatch(getTasksAsync());
+ } else {
+ const query = setQueryObject(queryObject);
+ dispatch(getTasksAsync(query));
+ }
},
(error: any) => {
dispatch(deleteTaskError(error));
diff --git a/cvat-ui/src/components/app/app.test.tsx b/cvat-ui/src/components/app/app.test.tsx
index e9957993..21ab2b40 100644
--- a/cvat-ui/src/components/app/app.test.tsx
+++ b/cvat-ui/src/components/app/app.test.tsx
@@ -1,7 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
+
import App from './app';
+
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(, div);
diff --git a/cvat-ui/src/components/app/app.tsx b/cvat-ui/src/components/app/app.tsx
index 07601089..a0cb97c6 100644
--- a/cvat-ui/src/components/app/app.tsx
+++ b/cvat-ui/src/components/app/app.tsx
@@ -2,34 +2,47 @@ import React, { PureComponent } from 'react';
import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
-import { loginAsync } from '../../actions/auth.actions';
-import Dashboard from '../dashboard/dashboard';
-import Login from '../login/login';
-import NotFound from '../not-found/not-found';
+import DashboardPage from '../dashboard-page/dashboard-page';
+import LoginPage from '../login-page/login-page';
+import RegisterPage from '../register-page/register-page';
+import PageNotFound from '../page-not-found/page-not-found';
import './app.scss';
-class App extends PureComponent {
- componentDidMount() {
- // TODO: remove when proper login flow (with router) will be implemented
- this.props.dispatch(
- loginAsync(
- process.env.REACT_APP_LOGIN as string,
- process.env.REACT_APP_PASSWORD as string,
- ),
- );
- }
+const ProtectedRoute = ({ component: Component, ...rest }: any) => {
+ return (
+ {
+ return localStorage.getItem('session') ? (
+
+ ) : (
+
+ );
+ } }
+ />
+ );
+};
+class App extends PureComponent {
render() {
return(
-
-
-
+
+
+
+
);
diff --git a/cvat-ui/src/components/dashboard/content/dashboard-content.scss b/cvat-ui/src/components/dashboard-page/content/dashboard-content.scss
similarity index 100%
rename from cvat-ui/src/components/dashboard/content/dashboard-content.scss
rename to cvat-ui/src/components/dashboard-page/content/dashboard-content.scss
diff --git a/cvat-ui/src/components/dashboard/content/dashboard-content.test.tsx b/cvat-ui/src/components/dashboard-page/content/dashboard-content.test.tsx
similarity index 99%
rename from cvat-ui/src/components/dashboard/content/dashboard-content.test.tsx
rename to cvat-ui/src/components/dashboard-page/content/dashboard-content.test.tsx
index 37a04805..0aa28ae2 100644
--- a/cvat-ui/src/components/dashboard/content/dashboard-content.test.tsx
+++ b/cvat-ui/src/components/dashboard-page/content/dashboard-content.test.tsx
@@ -1,7 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
+
import DashboardContent from './dashboard-content';
+
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(, div);
diff --git a/cvat-ui/src/components/dashboard/content/dashboard-content.tsx b/cvat-ui/src/components/dashboard-page/content/dashboard-content.tsx
similarity index 95%
rename from cvat-ui/src/components/dashboard/content/dashboard-content.tsx
rename to cvat-ui/src/components/dashboard-page/content/dashboard-content.tsx
index 8581c30b..ef95db75 100644
--- a/cvat-ui/src/components/dashboard/content/dashboard-content.tsx
+++ b/cvat-ui/src/components/dashboard-page/content/dashboard-content.tsx
@@ -1,5 +1,7 @@
import React, { Component } from 'react';
+import { withRouter } from 'react-router-dom';
+
import { connect } from 'react-redux';
import { deleteTaskAsync } from '../../../actions/tasks.actions';
@@ -120,7 +122,7 @@ class DashboardContent extends Component {
okType: 'danger',
centered: true,
onOk() {
- return self.props.dispatch(deleteTaskAsync(task));
+ return self.props.dispatch(deleteTaskAsync(task, self.props.history));
},
cancelText: 'No',
onCancel() {
@@ -142,4 +144,4 @@ const mapStateToProps = (state: any) => {
return state.tasks;
};
-export default connect(mapStateToProps)(DashboardContent);
+export default withRouter(connect(mapStateToProps)(DashboardContent) as any);
diff --git a/cvat-ui/src/components/dashboard/dashboard.scss b/cvat-ui/src/components/dashboard-page/dashboard-page.scss
similarity index 100%
rename from cvat-ui/src/components/dashboard/dashboard.scss
rename to cvat-ui/src/components/dashboard-page/dashboard-page.scss
diff --git a/cvat-ui/src/components/dashboard/dashboard.test.tsx b/cvat-ui/src/components/dashboard-page/dashboard-page.test.tsx
similarity index 83%
rename from cvat-ui/src/components/dashboard/dashboard.test.tsx
rename to cvat-ui/src/components/dashboard-page/dashboard-page.test.tsx
index 956dcb5f..4a12c570 100644
--- a/cvat-ui/src/components/dashboard/dashboard.test.tsx
+++ b/cvat-ui/src/components/dashboard-page/dashboard-page.test.tsx
@@ -1,6 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
-import Dashboard from './dashboard';
+
+import Dashboard from './dashboard-page';
+
it('renders without crashing', () => {
const div = document.createElement('div');
diff --git a/cvat-ui/src/components/dashboard/dashboard.tsx b/cvat-ui/src/components/dashboard-page/dashboard-page.tsx
similarity index 73%
rename from cvat-ui/src/components/dashboard/dashboard.tsx
rename to cvat-ui/src/components/dashboard-page/dashboard-page.tsx
index d7a28adf..15429df1 100644
--- a/cvat-ui/src/components/dashboard/dashboard.tsx
+++ b/cvat-ui/src/components/dashboard-page/dashboard-page.tsx
@@ -3,6 +3,8 @@ import { Location, Action } from 'history';
import * as queryString from 'query-string';
+import setQueryObject from '../../utils/tasks-filter-dto'
+
import { connect } from 'react-redux';
import { getTasksAsync } from '../../actions/tasks.actions';
import { filterTasks } from '../../actions/tasks-filter.actions';
@@ -13,7 +15,8 @@ import DashboardHeader from './header/dashboard-header';
import DashboardContent from './content/dashboard-content';
import DashboardFooter from './footer/dashboard-footer';
-import './dashboard.scss';
+import './dashboard-page.scss';
+
class Dashboard extends PureComponent {
componentDidMount() {
@@ -21,7 +24,7 @@ class Dashboard extends PureComponent {
this.props.history.listen((location: Location, action: Action) => {
this.loadTasks(location.search);
- })
+ });
}
render() {
@@ -36,25 +39,11 @@ class Dashboard extends PureComponent {
private loadTasks = (params: any) => {
const query = queryString.parse(params);
- const queryObject = this.setQueryObject(query);
+ const queryObject = setQueryObject(query);
this.props.dispatch(filterTasks(queryObject));
this.props.dispatch(getTasksAsync(queryObject));
}
-
- private setQueryObject = (params: { search?: string, page?: string }): { search?: string, page?: number } => {
- const queryObject: { search?: string, page?: number } = {};
-
- if (params['search']) {
- queryObject.search = params.search.toString();
- }
-
- if (params['page']) {
- queryObject.page = parseInt(params.page);
- }
-
- return queryObject;
- }
}
const mapStateToProps = (state: any) => {
diff --git a/cvat-ui/src/components/dashboard/footer/dashboard-footer.scss b/cvat-ui/src/components/dashboard-page/footer/dashboard-footer.scss
similarity index 100%
rename from cvat-ui/src/components/dashboard/footer/dashboard-footer.scss
rename to cvat-ui/src/components/dashboard-page/footer/dashboard-footer.scss
diff --git a/cvat-ui/src/components/dashboard/footer/dashboard-footer.test.tsx b/cvat-ui/src/components/dashboard-page/footer/dashboard-footer.test.tsx
similarity index 99%
rename from cvat-ui/src/components/dashboard/footer/dashboard-footer.test.tsx
rename to cvat-ui/src/components/dashboard-page/footer/dashboard-footer.test.tsx
index b52f6d91..9ab621e1 100644
--- a/cvat-ui/src/components/dashboard/footer/dashboard-footer.test.tsx
+++ b/cvat-ui/src/components/dashboard-page/footer/dashboard-footer.test.tsx
@@ -1,7 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
+
import DashboardFooter from './dashboard-footer';
+
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(, div);
diff --git a/cvat-ui/src/components/dashboard/footer/dashboard-footer.tsx b/cvat-ui/src/components/dashboard-page/footer/dashboard-footer.tsx
similarity index 100%
rename from cvat-ui/src/components/dashboard/footer/dashboard-footer.tsx
rename to cvat-ui/src/components/dashboard-page/footer/dashboard-footer.tsx
diff --git a/cvat-ui/src/components/dashboard/header/dashboard-header.scss b/cvat-ui/src/components/dashboard-page/header/dashboard-header.scss
similarity index 100%
rename from cvat-ui/src/components/dashboard/header/dashboard-header.scss
rename to cvat-ui/src/components/dashboard-page/header/dashboard-header.scss
diff --git a/cvat-ui/src/components/dashboard/header/dashboard-header.test.tsx b/cvat-ui/src/components/dashboard-page/header/dashboard-header.test.tsx
similarity index 99%
rename from cvat-ui/src/components/dashboard/header/dashboard-header.test.tsx
rename to cvat-ui/src/components/dashboard-page/header/dashboard-header.test.tsx
index 417876ec..6ca75cf6 100644
--- a/cvat-ui/src/components/dashboard/header/dashboard-header.test.tsx
+++ b/cvat-ui/src/components/dashboard-page/header/dashboard-header.test.tsx
@@ -1,7 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
+
import DashboardHeader from './dashboard-header';
+
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(, div);
diff --git a/cvat-ui/src/components/dashboard/header/dashboard-header.tsx b/cvat-ui/src/components/dashboard-page/header/dashboard-header.tsx
similarity index 100%
rename from cvat-ui/src/components/dashboard/header/dashboard-header.tsx
rename to cvat-ui/src/components/dashboard-page/header/dashboard-header.tsx
diff --git a/cvat-ui/src/components/login/login.scss b/cvat-ui/src/components/login-page/login-page.scss
similarity index 100%
rename from cvat-ui/src/components/login/login.scss
rename to cvat-ui/src/components/login-page/login-page.scss
diff --git a/cvat-ui/src/components/login/login.test.tsx b/cvat-ui/src/components/login-page/login-page.test.tsx
similarity index 86%
rename from cvat-ui/src/components/login/login.test.tsx
rename to cvat-ui/src/components/login-page/login-page.test.tsx
index 885191e2..6bec1fa7 100644
--- a/cvat-ui/src/components/login/login.test.tsx
+++ b/cvat-ui/src/components/login-page/login-page.test.tsx
@@ -1,6 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
-import Login from './login';
+
+import Login from './login-page';
+
it('renders without crashing', () => {
const div = document.createElement('div');
diff --git a/cvat-ui/src/components/login/login.tsx b/cvat-ui/src/components/login-page/login-page.tsx
similarity index 90%
rename from cvat-ui/src/components/login/login.tsx
rename to cvat-ui/src/components/login-page/login-page.tsx
index 7e28942f..8269844a 100644
--- a/cvat-ui/src/components/login/login.tsx
+++ b/cvat-ui/src/components/login-page/login-page.tsx
@@ -4,18 +4,24 @@ import { connect } from 'react-redux';
import { loginAsync } from '../../actions/auth.actions';
import { Button, Icon, Input, Form, Col, Row } from 'antd';
-
-import './login.scss';
import Title from 'antd/lib/typography/Title';
+import './login-page.scss';
+
class LoginForm extends PureComponent {
+ componentWillMount() {
+ if (localStorage.getItem('session')) {
+ this.props.history.push('/dashboard');
+ }
+ }
+
render() {
const { getFieldDecorator } = this.props.form;
return (
-
+
+
+
+ );
+ }
+
+ private handleConfirmBlur = (event: any) => {
+ const { value } = event.target;
+
+ this.setState({ confirmDirty: this.state.confirmDirty || !!value });
+ };
+
+ private compareToFirstPassword = (rule: any, value: string, callback: Function) => {
+ const { form } = this.props;
+
+ if (value && value !== form.getFieldValue('password')) {
+ callback('Two passwords that you enter are inconsistent!');
+ } else {
+ callback();
+ }
+ };
+
+ private validateToNextPassword = (rule: any, value: string, callback: Function) => {
+ const { form } = this.props;
+
+ if (value && this.state.confirmDirty) {
+ form.validateFields(['passwordConfirmation'], { force: true });
+ }
+
+ callback();
+ };
+
+ private onSubmit = (event: any) => {
+ event.preventDefault();
+
+ this.props.form.validateFields((error: any, values: any) => {
+ if (!error) {
+ // this.props.dispatch(registerAsync(values.username, values.password, this.props.history));
+ }
+ });
+ }
+}
+
+const mapStateToProps = (state: any) => {
+ return state.authContext;
+};
+
+export default Form.create()(connect(mapStateToProps)(RegisterForm));
diff --git a/cvat-ui/src/index.tsx b/cvat-ui/src/index.tsx
index 5c66f5c5..d6d9cc2f 100644
--- a/cvat-ui/src/index.tsx
+++ b/cvat-ui/src/index.tsx
@@ -1,13 +1,15 @@
import React from 'react';
import ReactDOM from 'react-dom';
-import { Provider } from 'react-redux'
+import { Provider } from 'react-redux'
import configureStore from './store';
+import App from './components/app/app';
+
import * as serviceWorker from './serviceWorker';
import './index.scss';
-import App from './components/app/app';
+
ReactDOM.render(
diff --git a/cvat-ui/src/store.ts b/cvat-ui/src/store.ts
index 244f5187..acb780b5 100644
--- a/cvat-ui/src/store.ts
+++ b/cvat-ui/src/store.ts
@@ -1,14 +1,27 @@
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
+import { createLogger } from 'redux-logger'
import rootReducer from './reducers/root.reducer';
export default function configureStore(initialState = {}) {
+ const logger = createLogger({
+ collapsed: true,
+ });
+
+ const middlewares = [];
+
+ if (process.env.NODE_ENV === `development`) {
+ middlewares.push(logger);
+ }
+
+ middlewares.push(thunk);
+
return createStore(
rootReducer,
initialState,
compose(
- applyMiddleware(thunk),
+ applyMiddleware(...middlewares),
(window as any).__REDUX_DEVTOOLS_EXTENSION__
?
(window as any).__REDUX_DEVTOOLS_EXTENSION__({ trace: true })
diff --git a/cvat-ui/src/utils/.gitkeep b/cvat-ui/src/utils/.gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/cvat-ui/src/utils/tasks-filter-dto.ts b/cvat-ui/src/utils/tasks-filter-dto.ts
new file mode 100644
index 00000000..c57045dc
--- /dev/null
+++ b/cvat-ui/src/utils/tasks-filter-dto.ts
@@ -0,0 +1,13 @@
+export default (params: { search?: string, page?: string }): { search?: string, page?: number } => {
+ const queryObject: { search?: string, page?: number } = {};
+
+ if (params['search']) {
+ queryObject.search = params.search.toString();
+ }
+
+ if (params['page']) {
+ queryObject.page = parseInt(params.page);
+ }
+
+ return queryObject;
+}