diff --git a/cvat-ui/src/actions/auth.actions.ts b/cvat-ui/src/actions/auth.actions.ts index 35d9aeb2..864c4d4b 100644 --- a/cvat-ui/src/actions/auth.actions.ts +++ b/cvat-ui/src/actions/auth.actions.ts @@ -1,13 +1,33 @@ -export const login = (isAuthenticated: boolean) => (dispatch: any) => { +export const login = () => (dispatch: any) => { dispatch({ type: 'LOGIN', - payload: isAuthenticated, }); } -export const logout = (isAuthenticated: boolean) => (dispatch: any) => { +export const loginSuccess = () => (dispatch: any) => { dispatch({ - type: 'LOGOUT', - payload: isAuthenticated, + type: 'LOGIN_SUCCESS', }); } + +export const loginError = (error = {}) => (dispatch: any) => { + dispatch({ + type: 'LOGIN_ERROR', + payload: error, + }); +} + +export const loginAsync = (username: string, password: string) => { + return (dispatch: any) => { + dispatch(login()); + + return (window as any).cvat.server.login(username, password).then( + (authenticated: any) => { + dispatch(loginSuccess()); + }, + (error: any) => { + dispatch(loginError(error)); + }, + ); + }; +} diff --git a/cvat-ui/src/actions/tasks.actions.ts b/cvat-ui/src/actions/tasks.actions.ts index bca38134..481176b4 100644 --- a/cvat-ui/src/actions/tasks.actions.ts +++ b/cvat-ui/src/actions/tasks.actions.ts @@ -1,18 +1,68 @@ -export const getTasks = (tasks: []) => (dispatch: any) => { +export const getTasks = () => (dispatch: any, getState: any) => { dispatch({ type: 'GET_TASKS', + }); +} + +export const getTasksSuccess = (tasks: []) => (dispatch: any, getState: any) => { + dispatch({ + type: 'GET_TASKS_SUCCESS', payload: tasks, }); } +export const getTasksError = (error: {}) => (dispatch: any, getState: any) => { + dispatch({ + type: 'GET_TASKS_ERROR', + payload: error, + }); +} + +export const deleteTask = () => (dispatch: any, getState: any) => { + dispatch({ + type: 'DELETE_TASK', + }); +} + +export const deleteTaskSuccess = () => (dispatch: any, getState: any) => { + dispatch({ + type: 'DELETE_TASK_SUCCESS', + }); +} + +export const deleteTaskError = (error: {}) => (dispatch: any, getState: any) => { + dispatch({ + type: 'DELETE_TASK_ERROR', + payload: error, + }); +} + export const getTasksAsync = (queryObject = {}) => { return (dispatch: any) => { + dispatch(getTasks()); + return (window as any).cvat.tasks.get(queryObject).then( (tasks: any) => { - dispatch(getTasks(tasks)); + dispatch(getTasksSuccess(tasks)); + }, + (error: any) => { + dispatch(getTasksError(error)); + }, + ); + }; +} + +export const deleteTaskAsync = (task: any) => { + return (dispatch: any) => { + dispatch(deleteTask()); + + return task.delete().then( + (deleted: any) => { + dispatch(deleteTaskSuccess()); + dispatch(getTasksAsync()); }, (error: any) => { - console.log(error); + dispatch(deleteTaskError(error)); }, ); }; diff --git a/cvat-ui/src/components/app/app.tsx b/cvat-ui/src/components/app/app.tsx index 31db2996..07601089 100644 --- a/cvat-ui/src/components/app/app.tsx +++ b/cvat-ui/src/components/app/app.tsx @@ -2,24 +2,23 @@ import React, { PureComponent } from 'react'; import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom'; import { connect } from 'react-redux'; -import { login, logout } from '../../actions/auth.actions'; +import { loginAsync } from '../../actions/auth.actions'; import Dashboard from '../dashboard/dashboard'; +import Login from '../login/login'; import NotFound from '../not-found/not-found'; import './app.scss'; -declare const window: any; class App extends PureComponent { componentDidMount() { - window.cvat.server.login(process.env.REACT_APP_LOGIN, process.env.REACT_APP_PASSWORD).then( - (_response: any) => { - this.props.dispatch(login(true)); - }, - (_error: any) => { - this.props.dispatch(logout(false)); - } + // 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, + ), ); } @@ -29,6 +28,7 @@ class App extends PureComponent { + diff --git a/cvat-ui/src/components/dashboard/content/dashboard-content.tsx b/cvat-ui/src/components/dashboard/content/dashboard-content.tsx index e1675496..8581c30b 100644 --- a/cvat-ui/src/components/dashboard/content/dashboard-content.tsx +++ b/cvat-ui/src/components/dashboard/content/dashboard-content.tsx @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; -import { getTasksAsync } from '../../../actions/tasks.actions'; +import { deleteTaskAsync } from '../../../actions/tasks.actions'; import { Layout, Empty, Button, Modal, Col, Row } from 'antd'; import Title from 'antd/lib/typography/Title'; @@ -119,13 +119,8 @@ class DashboardContent extends Component { okText: 'Yes', okType: 'danger', centered: true, - onOk(closeFunction: Function) { - return task.delete().then( - () => { - self.props.dispatch(getTasksAsync()); - closeFunction(); - }, - ); + onOk() { + return self.props.dispatch(deleteTaskAsync(task)); }, cancelText: 'No', onCancel() { diff --git a/cvat-ui/src/components/login/login.scss b/cvat-ui/src/components/login/login.scss new file mode 100644 index 00000000..18122d20 --- /dev/null +++ b/cvat-ui/src/components/login/login.scss @@ -0,0 +1,10 @@ +.login-form { + display: flex; + flex-direction: column; + justify-content: center; + height: 100vh; + + &__title { + + } +} diff --git a/cvat-ui/src/components/login/login.test.tsx b/cvat-ui/src/components/login/login.test.tsx new file mode 100644 index 00000000..885191e2 --- /dev/null +++ b/cvat-ui/src/components/login/login.test.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Login from './login'; + +it('renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); + ReactDOM.unmountComponentAtNode(div); +}); diff --git a/cvat-ui/src/components/login/login.tsx b/cvat-ui/src/components/login/login.tsx new file mode 100644 index 00000000..7e28942f --- /dev/null +++ b/cvat-ui/src/components/login/login.tsx @@ -0,0 +1,76 @@ +import React, { PureComponent } from 'react'; + +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'; + + +class LoginForm extends PureComponent { + render() { + const { getFieldDecorator } = this.props.form; + + return ( + + +
+ Login + + + {getFieldDecorator('username', { + rules: [{ required: true, message: 'Please enter your username!' }], + })( + } + type="text" + name="username" + placeholder="Username" + />, + )} + + + + {getFieldDecorator('password', { + rules: [{ required: true, message: 'Please enter your password!' }], + })( + } + type="password" + name="password" + placeholder="Password" + />, + )} + + + + + + + Have not registered yet? Register here. +
+ +
+ ); + } + + private onSubmit = (event: any) => { + event.preventDefault(); + + this.props.form.validateFields((error: any, values: any) => { + if (!error) { + this.props.dispatch(loginAsync(values.username, values.password)) + } + }); + } +} + +const mapStateToProps = (state: any) => { + return state.authContext; +}; + +export default Form.create()(connect(mapStateToProps)(LoginForm)); diff --git a/cvat-ui/src/reducers/auth.reducer.ts b/cvat-ui/src/reducers/auth.reducer.ts index 751df369..cb26b983 100644 --- a/cvat-ui/src/reducers/auth.reducer.ts +++ b/cvat-ui/src/reducers/auth.reducer.ts @@ -1,9 +1,27 @@ -export default (state = {}, action: any) => { +export default ( + state = { + isAuthenticated: false, + isFetching: false, + error: null, + }, + action: any, +) => { switch (action.type) { case 'LOGIN': - return { ...state, isAuthenticated: action.payload }; - case 'LOGOUT': - return { ...state, isAuthenticated: action.payload }; + return Object.assign({}, state, { + isFetching: true, + }); + case 'LOGIN_SUCCESS': + return Object.assign({}, state, { + isFetching: false, + isAuthenticated: true, + }); + case 'LOGIN_ERROR': + return Object.assign({}, state, { + isFetching: false, + isAuthenticated: false, + error: action.payload, + }); default: return state; } diff --git a/cvat-ui/src/reducers/tasks-filter.reducer.ts b/cvat-ui/src/reducers/tasks-filter.reducer.ts index 648f3814..beba947e 100644 --- a/cvat-ui/src/reducers/tasks-filter.reducer.ts +++ b/cvat-ui/src/reducers/tasks-filter.reducer.ts @@ -1,11 +1,16 @@ -export default (state = { searchQuery: '', currentPage: 1 }, action: any) => { +export default ( + state = { + searchQuery: '', + currentPage: 1 + }, + action: any, +) => { switch (action.type) { case 'FILTER_TASKS': - return { - ...state, + return Object.assign({}, state, { searchQuery: action.payload.search, currentPage: action.payload.page, - }; + }); default: return state; } diff --git a/cvat-ui/src/reducers/tasks.reducer.ts b/cvat-ui/src/reducers/tasks.reducer.ts index 574c6afb..e8ad2430 100644 --- a/cvat-ui/src/reducers/tasks.reducer.ts +++ b/cvat-ui/src/reducers/tasks.reducer.ts @@ -1,11 +1,44 @@ -export default (state: any = { tasks: [], tasksCount: 0 }, action: any) => { +export default ( + state: any = { + tasks: [], + tasksCount: 0, + isFetching: false, + error: null, + }, + action: any, +) => { switch (action.type) { case 'GET_TASKS': - return { - ...state, + return Object.assign({}, state, { + isFetching: true, + }); + case 'GET_TASKS_SUCCESS': + return Object.assign({}, state, { + isFetching: false, tasks: Array.from(action.payload.values()), tasksCount: action.payload.count, - }; + }); + case 'GET_TASKS_ERROR': + return Object.assign({}, state, { + isFetching: false, + error: action.payload, + }); + + case 'DELETE_TASK': + return Object.assign({}, state, { + isFetching: true, + }); + + case 'DELETE_TASK_SUCCESS': + return Object.assign({}, state, { + isFetching: false, + }); + + case 'DELETE_TASK_ERROR': + return Object.assign({}, state, { + isFetching: false, + error: action.payload, + }); default: return state; }