Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to configure user agreements for the register user form #1464

Merged
merged 13 commits into from
May 19, 2020
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) {
azhavoro marked this conversation as resolved.
Show resolved Hide resolved
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