Skip to content

Commit

Permalink
Ability to configure user agreements for the register user form (#1464)
Browse files Browse the repository at this point in the history
  • Loading branch information
azhavoro committed May 19, 2020
1 parent fc2f02a commit 380be58
Show file tree
Hide file tree
Showing 29 changed files with 548 additions and 19 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- cvat-ui: added cookie policy drawer for login page (<https://github.com/opencv/cvat/pull/1511>)
- Added `datumaro_project` export format (https://github.com/opencv/cvat/pull/1352)
- Ability to configure user agreements for the user registration form (https://github.com/opencv/cvat/pull/1464)

### Changed
- Downloaded file name in annotations export became more informative (https://github.com/opencv/cvat/pull/1352)
Expand Down
1 change: 1 addition & 0 deletions Dockerfile.ui
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ ARG http_proxy
ARG https_proxy
ARG no_proxy
ARG socks_proxy
ARG PUBLIC_INSTANCE

ENV TERM=xterm \
http_proxy=${http_proxy} \
Expand Down
9 changes: 7 additions & 2 deletions cvat-core/src/api-implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,15 @@
return new AnnotationFormats(result);
};

cvat.server.userAgreements.implementation = async () => {
const result = await serverProxy.server.userAgreements();
return result;
};

cvat.server.register.implementation = async (username, firstName, lastName,
email, password1, password2) => {
email, password1, password2, userConfirmations) => {
await serverProxy.server.register(username, firstName, lastName, email,
password1, password2);
password1, password2, userConfirmations);
};

cvat.server.login.implementation = async (username, password) => {
Expand Down
20 changes: 18 additions & 2 deletions cvat-core/src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,21 @@ function build() {
return result;
},
/**
* Method returns user agreements that the user must accept
* @method userAgreements
* @async
* @memberof module:API.cvat.server
* @returns {Object[]}
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/
async userAgreements() {
const result = await PluginRegistry
.apiWrapper(cvat.server.userAgreements);
return result;
},
/**
* Method allows to register on a server
* @method register
* @async
Expand All @@ -129,13 +144,14 @@ function build() {
* @param {string} email A email address for the new account
* @param {string} password1 A password for the new account
* @param {string} password2 The confirmation password for the new account
* @param {Object} userConfirmations An user confirmations of terms of use if needed
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/
async register(username, firstName, lastName, email, password1, password2) {
async register(username, firstName, lastName, email, password1, password2, userConfirmations) {
const result = await PluginRegistry
.apiWrapper(cvat.server.register, username, firstName,
lastName, email, password1, password2);
lastName, email, password1, password2, userConfirmations);
return result;
},
/**
Expand Down
20 changes: 19 additions & 1 deletion cvat-core/src/server-proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,23 @@
return response.data;
}

async function register(username, firstName, lastName, email, password1, password2) {

async function userAgreements() {
const { backendAPI } = config;
let response = null;
try {
response = await Axios.get(`${backendAPI}/restrictions/user-agreements`, {
proxy: config.proxy,
});

} catch (errorData) {
throw generateError(errorData);
}

return response.data;
}

async function register(username, firstName, lastName, email, password1, password2, confirmations) {
let response = null;
try {
const data = JSON.stringify({
Expand All @@ -164,6 +180,7 @@
email,
password1,
password2,
confirmations,
});
response = await Axios.post(`${config.backendAPI}/auth/register`, data, {
proxy: config.proxy,
Expand Down Expand Up @@ -657,6 +674,7 @@
authorized,
register,
request: serverRequest,
userAgreements,
}),
writable: false,
},
Expand Down
4 changes: 3 additions & 1 deletion cvat-ui/src/actions/auth-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// SPDX-License-Identifier: MIT

import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import { UserConfirmation } from 'components/register-page/register-form';
import getCore from 'cvat-core-wrapper';

const cvat = getCore();
Expand Down Expand Up @@ -44,13 +45,14 @@ export const registerAsync = (
email: string,
password1: string,
password2: string,
confirmations: UserConfirmation[],
): ThunkAction => async (
dispatch,
) => {
dispatch(authActions.register());

try {
await cvat.server.register(username, firstName, lastName, email, password1, password2);
await cvat.server.register(username, firstName, lastName, email, password1, password2, confirmations);
const users = await cvat.users.get({ self: true });

dispatch(authActions.registerSuccess(users[0]));
Expand Down
38 changes: 38 additions & 0 deletions cvat-ui/src/actions/useragreements-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT

import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import getCore from 'cvat-core-wrapper';
import { UserAgreement } from 'reducers/interfaces'

const core = getCore();

export enum UserAgreementsActionTypes {
GET_USER_AGREEMENTS = 'GET_USER_AGREEMENTS',
GET_USER_AGREEMENTS_SUCCESS = 'GET_USER_AGREEMENTS_SUCCESS',
GET_USER_AGREEMENTS_FAILED = 'GET_USER_AGREEMENTS_FAILED',
}

const userAgreementsActions = {
getUserAgreements: () => createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS),
getUserAgreementsSuccess: (userAgreements: UserAgreement[]) =>
createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS_SUCCESS, userAgreements),
getUserAgreementsFailed: (error: any) =>
createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS_FAILED, { error }),
};

export type UserAgreementsActions = ActionUnion<typeof userAgreementsActions>;

export const getUserAgreementsAsync = (): ThunkAction => async (dispatch): Promise<void> => {
dispatch(userAgreementsActions.getUserAgreements());

try {
const userAgreements = await core.server.userAgreements();
dispatch(
userAgreementsActions.getUserAgreementsSuccess(userAgreements),
);
} catch (error) {
dispatch(userAgreementsActions.getUserAgreementsFailed(error));
}
};
13 changes: 12 additions & 1 deletion cvat-ui/src/components/cvat-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ interface CVATAppProps {
loadUsers: () => void;
loadAbout: () => void;
verifyAuthorized: () => void;
loadUserAgreements: () => void;
initPlugins: () => void;
resetErrors: () => void;
resetMessages: () => void;
Expand All @@ -51,14 +52,16 @@ interface CVATAppProps {
installedAutoAnnotation: boolean;
installedTFAnnotation: boolean;
installedTFSegmentation: boolean;
userAgreementsFetching: boolean,
userAgreementsInitialized: boolean,
notifications: NotificationsState;
user: any;
}

class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentProps> {
public componentDidMount(): void {
const core = getCore();
const { verifyAuthorized } = this.props;
const { verifyAuthorized, loadUserAgreements } = this.props;
configure({ ignoreRepeatedEventsWhenKeyHeldDown: false });

// Logger configuration
Expand All @@ -77,6 +80,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
loadFormats,
loadUsers,
loadAbout,
loadUserAgreements,
initPlugins,
userInitialized,
userFetching,
Expand All @@ -89,6 +93,8 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
pluginsInitialized,
pluginsFetching,
user,
userAgreementsFetching,
userAgreementsInitialized,
} = this.props;

this.showErrors();
Expand All @@ -99,6 +105,11 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
return;
}

if (!userAgreementsInitialized && !userAgreementsFetching) {
loadUserAgreements();
return;
}

if (user == null) {
return;
}
Expand Down
71 changes: 71 additions & 0 deletions cvat-ui/src/components/register-page/register-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,30 @@ import Form, { FormComponentProps } from 'antd/lib/form/Form';
import Button from 'antd/lib/button';
import Icon from 'antd/lib/icon';
import Input from 'antd/lib/input';
import Checkbox from 'antd/lib/checkbox';

import patterns from 'utils/validation-patterns';

import { UserAgreement } from 'reducers/interfaces'

export interface UserConfirmation {
name: string;
value: boolean;
}

export interface RegisterData {
username: string;
firstName: string;
lastName: string;
email: string;
password1: string;
password2: string;
confirmations: UserConfirmation[];
}

type RegisterFormProps = {
fetching: boolean;
userAgreements: UserAgreement[],
onSubmit(registerData: RegisterData): void;
} & FormComponentProps;

Expand Down Expand Up @@ -70,15 +80,43 @@ class RegisterFormComponent extends React.PureComponent<RegisterFormProps> {
callback();
};

private validateAgrement = (agreement: any, value: any, callback: any): void => {
const { userAgreements } = this.props;
let isValid: boolean = true;
for (const userAgreement of userAgreements) {
if (agreement.field === userAgreement.name
&& userAgreement.required && !value) {
isValid = false;
callback(`You must accept the ${userAgreement.displayText} to continue!`);
break;
}
}
if (isValid) {
callback();
}
};

private handleSubmit = (e: React.FormEvent): void => {
e.preventDefault();
const {
form,
onSubmit,
userAgreements,
} = this.props;

form.validateFields((error, values): void => {
if (!error) {
values.confirmations = []

for (const userAgreement of userAgreements) {

values.confirmations.push({
name: userAgreement.name,
value: values[userAgreement.name]
});
delete values[userAgreement.name];
}

onSubmit(values);
}
});
Expand Down Expand Up @@ -214,6 +252,38 @@ class RegisterFormComponent extends React.PureComponent<RegisterFormProps> {
);
}

private renderUserAgreements(): JSX.Element[] {
const { form, userAgreements } = this.props;
const getUserAgreementsElements = () =>
{
const agreementsList: JSX.Element[] = [];
for (const userAgreement of userAgreements) {
agreementsList.push(
<Form.Item key={userAgreement.name}>
{form.getFieldDecorator(userAgreement.name, {
initialValue: false,
valuePropName: 'checked',
rules: [{
required: true,
message: 'You must accept to continue!',
}, {
validator: this.validateAgrement,
}]
})(
<Checkbox>
I read and accept the <a rel='noopener noreferrer' target='_blank'
href={ userAgreement.url }>{ userAgreement.displayText }</a>
</Checkbox>
)}
</Form.Item>
);
}
return agreementsList;
}

return getUserAgreementsElements();
}

public render(): JSX.Element {
const { fetching } = this.props;

Expand All @@ -225,6 +295,7 @@ class RegisterFormComponent extends React.PureComponent<RegisterFormProps> {
{this.renderEmailField()}
{this.renderPasswordField()}
{this.renderPasswordConfirmationField()}
{this.renderUserAgreements()}

<Form.Item>
<Button
Expand Down
10 changes: 8 additions & 2 deletions cvat-ui/src/components/register-page/register-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ import Title from 'antd/lib/typography/Title';
import Text from 'antd/lib/typography/Text';
import { Row, Col } from 'antd/lib/grid';

import RegisterForm, { RegisterData } from './register-form';
import { UserAgreement } from 'reducers/interfaces'
import RegisterForm, { RegisterData, UserConfirmation } from './register-form';
import CookieDrawer from 'components/login-page/cookie-policy-drawer';

interface RegisterPageComponentProps {
fetching: boolean;
userAgreements: UserAgreement[];
onRegister: (username: string, firstName: string,
lastName: string, email: string,
password1: string, password2: string) => void;
password1: string, password2: string,
confirmations: UserConfirmation[]) => void;
}

function RegisterPageComponent(
Expand All @@ -32,6 +35,7 @@ function RegisterPageComponent(

const {
fetching,
userAgreements,
onRegister,
} = props;

Expand All @@ -42,6 +46,7 @@ function RegisterPageComponent(
<Title level={2}> Create an account </Title>
<RegisterForm
fetching={fetching}
userAgreements={userAgreements}
onSubmit={(registerData: RegisterData): void => {
onRegister(
registerData.username,
Expand All @@ -50,6 +55,7 @@ function RegisterPageComponent(
registerData.email,
registerData.password1,
registerData.password2,
registerData.confirmations,
);
}}
/>
Expand Down
Loading

0 comments on commit 380be58

Please sign in to comment.