New authentication UI (#5181)

* Add social accounts authentication && improve email confirmation

* Pass env variables to docker

* Update helm chart

* Update email verification templates

* Small refactoring

* Send email verification && redirect to /auth/email-verification-sent

* added new login form

* added main assets

* added main social buttons

* changed reset password form

* refactored reset password page

* Fix helm chart

* Fix typo

* Pass enabled advanced auth methods to client

* Rename class

* Fix

* Fix helm

* Fix github scope

* changed register page

* adjusted no social auth methods

* Some fixes

* Fix schema generation

* Fixes

* Apply comments

* Update changelog

* added responsiveness, refactored inputs

* cleanup

* fixed email-confirmed.tsx

* updated reset page, changed style on register page

* added fonts, fixed some ui problems

* removed some code

* fixed index.html

* made resizing less expressed

* fixed form sizing, issue#5166

* fixed submiting form by enter

* made toggle bigger, fixed headers

* updated versions

* removed extra lines

* fixed fields on form

* changed tests

* fixed more tests

* fixed comments

* reverted header

* added grid-unit-size for height, removed for fonts

* added new animation

Co-authored-by: Maya <maya17grd@gmail.com>
main
Kirill Lakhov 3 years ago committed by GitHub
parent e5d01359aa
commit 92ba1ab845
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -18,7 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Mask tools are supported now (brush, eraser, polygon-plus, polygon-minus, returning masks
from online detectors & interactors) (<https://github.com/opencv/cvat/pull/4543>)
- Added Webhooks (<https://github.com/opencv/cvat/pull/4863>)
- Authentication with social accounts google & github (<https://github.com/opencv/cvat/pull/5147>)
- Authentication with social accounts google & github (<https://github.com/opencv/cvat/pull/5147>, <https://github.com/opencv/cvat/pull/5181>)
- REST API tests to export job datasets & annotations and validate their structure (<https://github.com/opencv/cvat/pull/5160>)
### Changed

@ -1,6 +1,6 @@
{
"name": "cvat-core",
"version": "7.1.0",
"version": "7.2.0",
"description": "Part of Computer Vision Tool which presents an interface for client-side integration",
"main": "src/api.ts",
"scripts": {

@ -63,8 +63,7 @@ const config = require('./config').default;
firstName,
lastName,
email,
password1,
password2,
password,
userConfirmations,
) => {
const user = await serverProxy.server.register(
@ -72,8 +71,7 @@ const config = require('./config').default;
firstName,
lastName,
email,
password1,
password2,
password,
userConfirmations,
);

@ -127,22 +127,20 @@ function build() {
* @param {string} firstName A first name for the new account
* @param {string} lastName A last name for the new account
* @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 {string} password A password for the new account
* @param {Object} userConfirmations An user confirmations of terms of use if needed
* @returns {Object} response data
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/
async register(username, firstName, lastName, email, password1, password2, userConfirmations) {
async register(username, firstName, lastName, email, password, userConfirmations) {
const result = await PluginRegistry.apiWrapper(
cvat.server.register,
username,
firstName,
lastName,
email,
password1,
password2,
password,
userConfirmations,
);
return result;
@ -173,6 +171,10 @@ function build() {
const result = await PluginRegistry.apiWrapper(cvat.server.logout);
return result;
},
async advancedAuthentication() {
const result = await PluginRegistry.apiWrapper(cvat.server.advancedAuthentication);
return result;
},
/**
* Method returns enabled advanced authentication methods
* @method advancedAuthentication

@ -313,7 +313,7 @@ class ServerProxy {
}
}
async function register(username, firstName, lastName, email, password1, password2, confirmations) {
async function register(username, firstName, lastName, email, password, confirmations) {
let response = null;
try {
const data = JSON.stringify({
@ -321,8 +321,8 @@ class ServerProxy {
first_name: firstName,
last_name: lastName,
email,
password1,
password2,
password1: password,
password2: password,
confirmations,
});
response = await Axios.post(`${config.backendAPI}/auth/register`, data, {

@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.43.0",
"version": "1.43.1",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {

@ -91,8 +91,7 @@ export const registerAsync = (
firstName: string,
lastName: string,
email: string,
password1: string,
password2: string,
password: string,
confirmations: UserConfirmation[],
): ThunkAction => async (dispatch) => {
dispatch(authActions.register());
@ -103,8 +102,7 @@ export const registerAsync = (
firstName,
lastName,
email,
password1,
password2,
password,
confirmations,
);

@ -0,0 +1,3 @@
<svg width="32" height="14" viewBox="0 0 35 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M34.4685 7.59028C34.232 7.36755 33.9177 7.24668 33.5931 7.25014L4.78619 7.25013L9.72432 2.312C9.96605 2.07718 10.1025 1.7543 10.1025 1.41589C10.1025 1.07747 9.96605 0.754591 9.7226 0.518043C9.22878 0.0380432 8.43799 0.0484026 7.95626 0.543943L1.01353 7.48668C0.903022 7.56611 0.806332 7.6628 0.72518 7.77503L0 8.50021L0.721727 9.22194C0.802879 9.33417 0.901296 9.43258 1.01353 9.51373L7.95627 16.4565C8.27051 16.7811 8.73324 16.9088 9.17008 16.7932C9.60692 16.6809 9.94879 16.3391 10.061 15.9022C10.1767 15.4654 10.0489 15.0027 9.72433 14.6884L4.78619 9.75028H33.5931C34.1077 9.75719 34.5756 9.44812 34.7707 8.96985C34.9623 8.49158 34.8432 7.94424 34.4685 7.59028Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 793 B

@ -0,0 +1,5 @@
<svg width="15" height="15" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.2">
<path d="M10.9948 22C12.5049 22 13.9254 21.7138 15.2562 21.1415C16.5871 20.5692 17.7558 19.7762 18.7626 18.7626C19.7693 17.7489 20.5588 16.5802 21.1312 15.2562C21.7104 13.9323 22 12.5153 22 11.0052C22 9.48817 21.7104 8.0677 21.1312 6.74377C20.5588 5.41984 19.7693 4.25106 18.7626 3.23742C17.7558 2.22379 16.5871 1.43081 15.2562 0.858486C13.9254 0.286162 12.5049 0 10.9948 0C9.48472 0 8.06425 0.286162 6.73343 0.858486C5.4095 1.43081 4.24071 2.22379 3.22708 3.23742C2.22034 4.25106 1.43081 5.41984 0.858486 6.74377C0.286162 8.0677 0 9.48817 0 11.0052C0 12.5153 0.286162 13.9323 0.858486 15.2562C1.43081 16.5802 2.22034 17.7489 3.22708 18.7626C4.24071 19.7762 5.41294 20.5692 6.74377 21.1415C8.0746 21.7138 9.49162 22 10.9948 22ZM7.78843 15.4528C7.44366 15.4528 7.1506 15.3321 6.90926 15.0907C6.66792 14.8494 6.54725 14.5494 6.54725 14.1909C6.54725 13.8599 6.67137 13.5737 6.91961 13.3324L9.23648 11.0155L6.91961 8.69864C6.67137 8.4504 6.54725 8.16079 6.54725 7.82981C6.54725 7.47124 6.66792 7.17474 6.90926 6.94029C7.1506 6.69895 7.44366 6.57828 7.78843 6.57828C8.15389 6.57828 8.46419 6.69895 8.71932 6.94029L11.0155 9.23648L13.3221 6.94029C13.5634 6.69205 13.8702 6.56794 14.2426 6.56794C14.5943 6.56794 14.8873 6.68861 15.1218 6.92995C15.3631 7.17129 15.4838 7.4678 15.4838 7.81946C15.4838 8.15734 15.3597 8.4504 15.1114 8.69864L12.7945 11.0155L15.1011 13.3324C15.3562 13.5668 15.4838 13.853 15.4838 14.1909C15.4838 14.5494 15.3631 14.8494 15.1218 15.0907C14.8804 15.3321 14.5805 15.4528 14.2219 15.4528C13.8496 15.4528 13.5427 15.3286 13.3014 15.0804L11.0155 12.8049L8.72967 15.0804C8.48143 15.3286 8.16769 15.4528 7.78843 15.4528Z" fill="black"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@ -0,0 +1,6 @@
<svg width="80" height="15" viewBox="0 0 80 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.63399 15C2.37083 15 0 12.5333 0 7.48889C0 2.46667 2.34908 0 6.63399 0H10.6576V2.62222H6.63399C3.78464 2.62222 2.56659 4.06667 2.56659 7.48889C2.56659 10.9333 3.80639 12.3778 6.63399 12.3778H15.0638V15H6.63399Z" fill="white"/>
<path d="M22.412 0L27.2466 13.1004C27.7814 14.5415 28.8724 15 30.2842 15C31.7817 15 32.7657 14.476 33.3005 13.1004L38.3918 0H35.5895L30.9902 11.8559C30.8404 12.2052 30.6265 12.3581 30.2842 12.3581C29.942 12.3581 29.7067 12.2052 29.5783 11.8559L25.1929 0H22.412Z" fill="white"/>
<path d="M53.0989 3.1441C53.2487 2.79476 53.484 2.64192 53.8262 2.64192C54.1685 2.64192 54.4038 2.79476 54.5322 3.1441L58.9176 15H61.7199L56.8425 1.89956C56.3291 0.524017 55.3451 0 53.8476 0C52.3288 0 51.3448 0.524017 50.81 1.89956L45.74 15H48.521L53.0989 3.1441Z" fill="white"/>
<path d="M69.0681 0V2.5665H73.2661V15H75.802V2.5665H80V0H69.0681Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 981 B

@ -0,0 +1,5 @@
// Copyright (C) 2022 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
declare module '*.svg';

@ -0,0 +1,5 @@
<svg width="15" height="15" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.2">
<path d="M11 22C12.5046 22 13.9214 21.7116 15.2502 21.1348C16.5791 20.565 17.7462 19.7736 18.7517 18.7607C19.7641 17.7477 20.5586 16.58 21.1352 15.2576C21.7117 13.9281 22 12.5071 22 10.9947C22 9.48937 21.7117 8.07546 21.1352 6.753C20.5586 5.4235 19.7641 4.25228 18.7517 3.23933C17.7462 2.22638 16.5756 1.43501 15.2397 0.865228C13.9108 0.288409 12.4941 0 10.9895 0C9.48482 0 8.06807 0.288409 6.73921 0.865228C5.41035 1.43501 4.23969 2.22638 3.22723 3.23933C2.2218 4.25228 1.43081 5.4235 0.854267 6.753C0.284755 8.07546 0 9.48937 0 10.9947C0 12.5071 0.284755 13.9281 0.854267 15.2576C1.43081 16.58 2.22531 17.7477 3.23778 18.7607C4.25024 19.7736 5.4209 20.565 6.74976 21.1348C8.07862 21.7116 9.49537 22 11 22ZM11 15.458C10.1633 15.458 9.38287 15.3384 8.65868 15.0993C7.94151 14.8601 7.29115 14.5576 6.70757 14.1918C6.13103 13.819 5.63535 13.4286 5.22052 13.0206C4.80569 12.6056 4.48929 12.2187 4.27133 11.86C4.05337 11.4942 3.94439 11.2093 3.94439 11.0053C3.94439 10.7942 4.05337 10.5094 4.27133 10.1506C4.48929 9.78481 4.80569 9.39792 5.22052 8.98993C5.63535 8.5749 6.13103 8.18449 6.70757 7.8187C7.28412 7.44588 7.93448 7.1434 8.65868 6.91127C9.38287 6.6721 10.1633 6.55252 11 6.55252C11.8437 6.55252 12.6277 6.6721 13.3519 6.91127C14.0761 7.1434 14.7264 7.44588 15.303 7.8187C15.8795 8.18449 16.3717 8.5749 16.7795 8.98993C17.1873 9.39792 17.5002 9.78481 17.7181 10.1506C17.9431 10.5094 18.0556 10.7942 18.0556 11.0053C18.0556 11.2093 17.9431 11.4942 17.7181 11.86C17.5002 12.2187 17.1873 12.6056 16.7795 13.0206C16.3717 13.4286 15.8795 13.819 15.303 14.1918C14.7264 14.5576 14.0761 14.8601 13.3519 15.0993C12.6277 15.3384 11.8437 15.458 11 15.458ZM11 13.8647C11.5273 13.8647 12.0089 13.7346 12.4449 13.4743C12.8808 13.2141 13.2253 12.8659 13.4784 12.4297C13.7386 11.9936 13.8686 11.5188 13.8686 11.0053C13.8686 10.4777 13.7386 9.99584 13.4784 9.55971C13.2253 9.12358 12.8808 8.7789 12.4449 8.52566C12.0089 8.27242 11.5273 8.1458 11 8.1458C10.4727 8.1458 9.99105 8.27242 9.55513 8.52566C9.11921 8.7789 8.77117 9.12358 8.51103 9.55971C8.25088 9.99584 8.1208 10.4777 8.1208 11.0053C8.1208 11.5188 8.25088 11.9936 8.51103 12.4297C8.77117 12.8659 9.11921 13.2141 9.55513 13.4743C9.99105 13.7346 10.4727 13.8647 11 13.8647ZM11.0105 12.2609C10.659 12.2609 10.3602 12.1378 10.1141 11.8916C9.86801 11.6384 9.74497 11.3429 9.74497 11.0053C9.74497 10.6676 9.86801 10.3757 10.1141 10.1295C10.3602 9.87626 10.659 9.74964 11.0105 9.74964C11.348 9.74964 11.6398 9.87626 11.8859 10.1295C12.132 10.3757 12.255 10.6676 12.255 11.0053C12.255 11.3429 12.132 11.6384 11.8859 11.8916C11.6398 12.1378 11.348 12.2609 11.0105 12.2609Z" fill="black"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

@ -0,0 +1,11 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="black"/>
<rect width="100%" height="100%" fill="url(#paint0_radial_463_12470)" fill-opacity="0.6"/>
<defs>
<radialGradient id="paint0_radial_463_12470" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(419 523.5) rotate(68.1634) scale(1401.63 2071.53)">
<stop/>
<stop offset="0.489583" stop-color="#412515"/>
<stop offset="0.953125"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 490 B

@ -0,0 +1,3 @@
<svg width="33" height="32" viewBox="0 0 33 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.4042 0C7.34236 0 0 7.34236 0 16.4042C0 23.6504 4.69911 29.802 11.2191 31.97C12.0415 32.1195 12.3405 31.6176 12.3405 31.1797C12.3405 30.7899 12.3245 29.7593 12.3191 28.3923C7.75354 29.3801 6.79235 26.1922 6.79235 26.1922C6.04477 24.2966 4.97145 23.7893 4.97145 23.7893C3.48162 22.7747 5.08359 22.796 5.08359 22.796C6.72827 22.9135 7.59334 24.4834 7.59334 24.4834C9.05647 26.9932 11.4327 26.267 12.3672 25.8505C12.5167 24.7878 12.9439 24.0669 13.4085 23.6558C9.76668 23.2446 5.93797 21.8349 5.93797 15.5498C5.93797 13.7556 6.57876 12.2925 7.62538 11.1444C7.45984 10.7332 6.89381 9.06181 7.78558 6.80304C7.78558 6.80304 9.16327 6.36516 12.2978 8.4851C13.6061 8.12199 15.0105 7.94043 16.4042 7.93509C17.7979 7.94043 19.2023 8.12199 20.5106 8.4851C23.6451 6.36516 25.0174 6.80304 25.0174 6.80304C25.9145 9.06181 25.3538 10.7332 25.183 11.1444C26.2349 12.2925 26.865 13.7556 26.865 15.5498C26.865 21.8509 23.031 23.2339 19.3785 23.6451C19.9659 24.147 20.4892 25.1509 20.4892 26.6781C20.4892 28.8728 20.4732 30.6404 20.4732 31.1797C20.4732 31.6176 20.7669 32.1302 21.5999 31.97C28.1146 29.7966 32.8083 23.6504 32.8083 16.4042C32.8083 7.34236 25.466 0 16.4042 0Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@ -0,0 +1,7 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="16" fill="white"/>
<path d="M24.6426 16.2067C24.6426 15.5949 24.593 14.9798 24.4871 14.3779H15.998V17.8437H20.8594C20.6576 18.9614 20.0095 19.9502 19.0603 20.5786V22.8273H21.9606C23.6637 21.2598 24.6426 18.9449 24.6426 16.2067Z" fill="#4285F4"/>
<path d="M15.9987 25C18.426 25 20.473 24.203 21.9645 22.8273L19.0643 20.5786C18.2574 21.1275 17.2157 21.4384 16.002 21.4384C13.654 21.4384 11.6632 19.8543 10.9489 17.7246H7.95605V20.0428C9.48389 23.082 12.5958 25 15.9987 25Z" fill="#34A853"/>
<path d="M10.945 17.7245C10.568 16.6068 10.568 15.3964 10.945 14.2786V11.9604H7.95542C6.67892 14.5035 6.67892 17.4997 7.95542 20.0427L10.945 17.7245Z" fill="#FBBC04"/>
<path d="M15.9987 10.5615C17.2818 10.5417 18.5219 11.0245 19.4512 11.9108L22.0207 9.34123C20.3937 7.8134 18.2342 6.97342 15.9987 6.99987C12.5958 6.99987 9.48389 8.91793 7.95605 11.9604L10.9456 14.2786C11.6566 12.1456 13.6507 10.5615 15.9987 10.5615Z" fill="#EA4335"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -1,4 +1,5 @@
// Copyright (C) 2021-2022 Intel Corporation
// Copyright (C) 2022 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
@ -17,9 +18,9 @@ const { Countdown } = Statistic;
*/
function EmailConfirmationPage(): JSX.Element {
const linkRef = useRef();
const onFinish = () => {
linkRef.current.click();
const linkRef = useRef<HTMLAnchorElement>(null);
const onFinish = (): void => {
linkRef.current?.click();
};
return (
<Layout>

@ -3,11 +3,18 @@
//
// SPDX-License-Identifier: MIT
import React from 'react';
import React, { useCallback, useState } from 'react';
import Form from 'antd/lib/form';
import Button from 'antd/lib/button';
import Input from 'antd/lib/input';
import { UserOutlined, LockOutlined } from '@ant-design/icons';
import Icon from '@ant-design/icons';
import {
BackArrowIcon, ClearIcon,
} from 'icons';
import { Col, Row } from 'antd/lib/grid';
import Title from 'antd/lib/typography/Title';
import Text from 'antd/lib/typography/Text';
import { Link } from 'react-router-dom';
export interface LoginData {
credential: string;
@ -15,61 +22,139 @@ export interface LoginData {
}
interface Props {
renderResetPassword: boolean;
fetching: boolean;
socialAuthentication: JSX.Element | null;
onSubmit(loginData: LoginData): void;
}
function LoginFormComponent(props: Props): JSX.Element {
const { fetching, onSubmit } = props;
return (
<Form onFinish={onSubmit} className='login-form'>
<Form.Item
hasFeedback
name='credential'
rules={[
{
required: true,
message: 'Please specify a email or username',
},
]}
>
<Input
autoComplete='credential'
prefix={<UserOutlined style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Email or Username'
/>
</Form.Item>
const {
fetching, onSubmit, renderResetPassword, socialAuthentication,
} = props;
const [form] = Form.useForm();
const [credential, setCredential] = useState('');
<Form.Item
hasFeedback
name='password'
rules={[
{
required: true,
message: 'Please specify a password',
},
]}
const inputReset = useCallback((name: string):void => {
form.setFieldsValue({ [name]: '' });
}, [form]);
const forgotPasswordLink = (
<Col className='cvat-credentials-link'>
<Text strong>
<Link to={credential.includes('@') ?
`/auth/password/reset?credential=${credential}` : '/auth/password/reset'}
>
Forgot password?
</Link>
</Text>
</Col>
);
return (
<div className='cvat-login-form-wrapper'>
<Row justify='space-between' className='cvat-credentials-navigation'>
{
credential && (
<Col>
<Icon
component={BackArrowIcon}
onClick={() => {
setCredential('');
form.setFieldsValue({ credential: '' });
}}
/>
</Col>
)
}
{
!credential && (
<Row>
<Col className='cvat-credentials-link'>
<Text strong>
New user?&nbsp;
<Link to='/auth/register'>Create an account</Link>
</Text>
</Col>
</Row>
)
}
{
renderResetPassword && forgotPasswordLink
}
</Row>
<Col>
<Title level={2}> Sign in </Title>
</Col>
<Form
className={`cvat-login-form ${credential ? 'cvat-login-form-extended' : ''}`}
form={form}
onFinish={(loginData: LoginData) => {
onSubmit(loginData);
}}
>
<Input
autoComplete='current-password'
prefix={<LockOutlined style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Password'
type='password'
/>
</Form.Item>
<Form.Item>
<Button
type='primary'
loading={fetching}
disabled={fetching}
htmlType='submit'
className='login-form-button'
<Form.Item
className='cvat-credentials-form-item'
name='credential'
>
Sign in
</Button>
</Form.Item>
</Form>
<Input
autoComplete='credential'
placeholder='enter your email or username'
prefix={<Text>Email or username</Text>}
suffix={(
credential && (
<Icon
component={ClearIcon}
onClick={() => {
setCredential('');
inputReset('credential');
}}
/>
)
)}
onChange={(event) => {
const { value } = event.target;
setCredential(value);
if (!value) inputReset('credential');
}}
/>
</Form.Item>
{
credential && (
<Form.Item
className='cvat-credentials-form-item'
name='password'
rules={[
{
required: true,
message: 'Please specify a password',
},
]}
>
<Input.Password
autoComplete='current-password'
placeholder='enter your password'
prefix={
<Text>Password</Text>
}
/>
</Form.Item>
)
}
{
credential || !socialAuthentication ? (
<Form.Item>
<Button
className='cvat-credentials-action-button'
loading={fetching}
disabled={!credential}
htmlType='submit'
>
Next
</Button>
</Form.Item>
) : socialAuthentication
}
</Form>
</div>
);
}

@ -5,15 +5,12 @@
import React, { useEffect } from 'react';
import { RouteComponentProps, useHistory } from 'react-router';
import { Link, withRouter } from 'react-router-dom';
import Button from 'antd/lib/button';
import Title from 'antd/lib/typography/Title';
import Text from 'antd/lib/typography/Text';
import { withRouter } from 'react-router-dom';
import { Row, Col } from 'antd/lib/grid';
import Layout from 'antd/lib/layout';
import Space from 'antd/lib/space';
import { GithubOutlined, GooglePlusOutlined } from '@ant-design/icons';
import SigningLayout, { formSizes } from 'components/signing-common/signing-layout';
import SocialAccountLink from 'components/signing-common/social-account-link';
import { SocialGithubLogo, SocialGoogleLogo } from 'icons';
import LoginForm, { LoginData } from './login-form';
import { getCore } from '../../cvat-core-wrapper';
@ -32,14 +29,6 @@ interface LoginPageComponentProps {
function LoginPageComponent(props: LoginPageComponentProps & RouteComponentProps): JSX.Element {
const history = useHistory();
const { backendAPI } = cvat.config;
const sizes = {
style: {
width: 400,
},
};
const { Content } = Layout;
const {
fetching, renderResetPassword, hasEmailVerificationBeenSent,
googleAuthentication, githubAuthentication, onLogin, loadAdvancedAuthenticationMethods,
@ -54,74 +43,43 @@ function LoginPageComponent(props: LoginPageComponentProps & RouteComponentProps
}, []);
return (
<Layout>
<Content>
<Row style={{ height: '33%' }} />
<Row justify='center' align='middle'>
<Col {...sizes}>
<Title level={2}> Login </Title>
<SigningLayout>
<Col {...formSizes.wrapper}>
<Row justify='center'>
<Col {...formSizes.form}>
<LoginForm
fetching={fetching}
renderResetPassword={renderResetPassword}
socialAuthentication={(googleAuthentication || githubAuthentication) ? (
<div className='cvat-social-authentication'>
{githubAuthentication && (
<SocialAccountLink
icon={SocialGithubLogo}
href={`${backendAPI}/auth/github/login`}
className='cvat-social-authentication-github'
>
Continue with GitHub
</SocialAccountLink>
)}
{googleAuthentication && (
<SocialAccountLink
icon={SocialGoogleLogo}
href={`${backendAPI}/auth/google/login`}
className='cvat-social-authentication-google'
>
Continue with Google
</SocialAccountLink>
)}
</div>
) : null}
onSubmit={(loginData: LoginData): void => {
onLogin(loginData.credential, loginData.password);
}}
/>
{(googleAuthentication || githubAuthentication) &&
(
<>
<Row justify='center' align='top'>
<Col>
or
</Col>
</Row>
<Row justify='space-between' align='middle'>
{googleAuthentication && (
<Col span={11}>
<Button href={`${backendAPI}/auth/google/login`}>
<Space>
<GooglePlusOutlined />
Continue with Google
</Space>
</Button>
</Col>
)}
{githubAuthentication && (
<Col
span={11}
offset={googleAuthentication ? 1 : 0}
>
<Button href={`${backendAPI}/auth/github/login`}>
<Space>
<GithubOutlined />
Continue with Github
</Space>
</Button>
</Col>
)}
</Row>
</>
)}
<Row justify='start' align='top'>
<Col>
<Text strong>
New to CVAT? Create
<Link to='/auth/register'> an account</Link>
</Text>
</Col>
</Row>
{renderResetPassword && (
<Row justify='start' align='top'>
<Col>
<Text strong>
<Link to='/auth/password/reset'>Forgot your password?</Link>
</Text>
</Col>
</Row>
)}
</Col>
</Row>
</Content>
</Layout>
</Col>
</SigningLayout>
);
}

@ -3,17 +3,21 @@
//
// SPDX-License-Identifier: MIT
import React, { useState } from 'react';
import { UserAddOutlined, MailOutlined, LockOutlined } from '@ant-design/icons';
import React, { useCallback, useState } from 'react';
import Icon from '@ant-design/icons';
import Form, { RuleRender, RuleObject } from 'antd/lib/form';
import Button from 'antd/lib/button';
import Input from 'antd/lib/input';
import Text from 'antd/lib/typography/Text';
import Checkbox from 'antd/lib/checkbox';
import { Link } from 'react-router-dom';
import { BackArrowIcon } from 'icons';
import patterns from 'utils/validation-patterns';
import { UserAgreement } from 'reducers';
import { Row, Col } from 'antd/lib/grid';
import CVATSigningInput from 'components/signing-common/cvat-signing-input';
export interface UserConfirmation {
name: string;
@ -25,8 +29,7 @@ export interface RegisterData {
firstName: string;
lastName: string;
email: string;
password1: string;
password2: string;
password: string;
confirmations: UserConfirmation[];
}
@ -98,182 +101,182 @@ const validateAgreement: ((userAgreements: UserAgreement[]) => RuleRender) = (
});
function RegisterFormComponent(props: Props): JSX.Element {
const { fetching, userAgreements, onSubmit } = props;
const { fetching, onSubmit, userAgreements } = props;
const [form] = Form.useForm();
const [usernameEdited, setUsernameEdited] = useState(false);
const inputReset = useCallback((name: string):void => {
form.setFieldsValue({ [name]: '' });
}, [form]);
return (
<Form
form={form}
onFinish={(values: Record<string, string | boolean>) => {
const agreements = Object.keys(values)
.filter((key: string):boolean => key.startsWith('agreement:'));
const confirmations = agreements
.map((key: string): UserConfirmation => ({ name: key.split(':')[1], value: (values[key] as boolean) }));
const rest = Object.entries(values)
.filter((entry: (string | boolean)[]) => !agreements.includes(entry[0] as string));
onSubmit({
...(Object.fromEntries(rest) as any as RegisterData),
confirmations,
});
}}
className='register-form'
>
<Row gutter={8}>
<Col span={12}>
<Form.Item
hasFeedback
name='firstName'
rules={[
{
required: true,
message: 'Please specify a first name',
pattern: patterns.validateName.pattern,
},
]}
>
<Input
prefix={<UserAddOutlined style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='First name'
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
hasFeedback
name='lastName'
rules={[
{
required: true,
message: 'Please specify a last name',
pattern: patterns.validateName.pattern,
},
]}
>
<Input
prefix={<UserAddOutlined style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Last name'
/>
</Form.Item>
<div className={`cvat-register-form-wrapper ${userAgreements.length ? 'cvat-register-form-wrapper-extended' : ''}`}>
<Row justify='space-between' className='cvat-credentials-navigation'>
<Col>
<Link to='/auth/login'><Icon component={BackArrowIcon} /></Link>
</Col>
</Row>
<Form.Item
hasFeedback
name='email'
rules={[
{
type: 'email',
message: 'The input is not valid E-mail!',
},
{
required: true,
message: 'Please specify an email address',
},
]}
>
<Input
autoComplete='email'
prefix={<MailOutlined style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Email address'
onChange={(event) => {
const { value } = event.target;
if (!usernameEdited) {
const [username] = value.split('@');
form.setFieldsValue({ username });
}
}}
/>
</Form.Item>
<Form.Item
hasFeedback
name='username'
rules={[
{
required: true,
message: 'Please specify a username',
},
{
validator: validateUsername,
},
]}
>
<Input
prefix={<UserAddOutlined style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Username'
onChange={() => setUsernameEdited(true)}
/>
</Form.Item>
<Form.Item
hasFeedback
name='password1'
rules={[
{
required: true,
message: 'Please input your password!',
}, validatePassword,
]}
>
<Input.Password
autoComplete='new-password'
prefix={<LockOutlined style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Password'
/>
</Form.Item>
<Form
form={form}
onFinish={(values: Record<string, string | boolean>) => {
const agreements = Object.keys(values)
.filter((key: string):boolean => key.startsWith('agreement:'));
const confirmations = agreements
.map((key: string): UserConfirmation => ({ name: key.split(':')[1], value: (values[key] as boolean) }));
const rest = Object.entries(values)
.filter((entry: (string | boolean)[]) => !agreements.includes(entry[0] as string));
<Form.Item
hasFeedback
name='password2'
dependencies={['password1']}
rules={[
{
required: true,
message: 'Please confirm your password!',
}, validateConfirmation('password1'),
]}
onSubmit({
...(Object.fromEntries(rest) as any as RegisterData),
confirmations,
});
}}
className='cvat-register-form'
>
<Input.Password
autoComplete='new-password'
prefix={<LockOutlined style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Confirm password'
/>
</Form.Item>
{userAgreements.map((userAgreement: UserAgreement): JSX.Element => (
<Row gutter={8}>
<Col span={12}>
<Form.Item
className='cvat-credentials-form-item'
name='firstName'
rules={[
{
required: true,
message: 'Please specify a first name',
pattern: patterns.validateName.pattern,
},
]}
>
<CVATSigningInput
id='firstName'
placeholder='enter your first name'
prefix='First name'
onReset={() => inputReset('firstName')}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
className='cvat-credentials-form-item'
name='lastName'
rules={[
{
required: true,
message: 'Please specify a last name',
pattern: patterns.validateName.pattern,
},
]}
>
<CVATSigningInput
id='lastName'
placeholder='enter your last name'
prefix='Last name'
onReset={() => inputReset('lastName')}
/>
</Form.Item>
</Col>
</Row>
<Form.Item
name={`agreement:${userAgreement.name}`}
key={userAgreement.name}
initialValue={false}
valuePropName='checked'
className='cvat-credentials-form-item'
name='email'
rules={[
{
type: 'email',
message: 'The input is not valid E-mail!',
},
{
required: true,
message: 'You must accept to continue!',
}, validateAgreement(userAgreements),
message: 'Please specify an email address',
},
]}
>
<Checkbox>
{userAgreement.textPrefix}
{!!userAgreement.url &&
<a rel='noopener noreferrer' target='_blank' href={userAgreement.url}>
{` ${userAgreement.urlDisplayText}`}
</a>
}
</Checkbox>
<CVATSigningInput
id='email'
autoComplete='email'
placeholder='enter your email'
prefix='Email'
onReset={() => inputReset('email')}
onChange={(event) => {
const { value } = event.target;
if (!usernameEdited) {
const [username] = value.split('@');
form.setFieldsValue({ username });
}
}}
/>
</Form.Item>
))}
<Form.Item>
<Button
type='primary'
htmlType='submit'
className='register-form-button'
loading={fetching}
disabled={fetching}
<Form.Item
className='cvat-credentials-form-item'
name='username'
rules={[
{
required: true,
message: 'Please specify a username',
},
{
validator: validateUsername,
},
]}
>
<CVATSigningInput
id='username'
placeholder='enter your username'
prefix='Username'
onReset={() => inputReset('username')}
onChange={() => setUsernameEdited(true)}
/>
</Form.Item>
<Form.Item
className='cvat-credentials-form-item cvat-register-form-last-field'
name='password'
rules={[
{
required: true,
message: 'Please input your password!',
}, validatePassword,
]}
>
Submit
</Button>
</Form.Item>
</Form>
<Input.Password
placeholder='enter your password'
prefix={<Text>Password</Text>}
/>
</Form.Item>
{userAgreements.map((userAgreement: UserAgreement): JSX.Element => (
<Form.Item
className='cvat-agreements-form-item'
name={`agreement:${userAgreement.name}`}
key={userAgreement.name}
initialValue={false}
valuePropName='checked'
rules={[
{
required: true,
message: 'You must accept to continue!',
}, validateAgreement(userAgreements),
]}
>
<Checkbox>
{userAgreement.textPrefix}
{!!userAgreement.url && (
<a rel='noopener noreferrer' target='_blank' href={userAgreement.url}>
{` ${userAgreement.urlDisplayText}`}
</a>
)}
</Checkbox>
</Form.Item>
))}
<Form.Item>
<Button
type='primary'
htmlType='submit'
className='cvat-credentials-action-button'
loading={fetching}
disabled={fetching}
>
Create account
</Button>
</Form.Item>
</Form>
</div>
);
}

@ -3,16 +3,13 @@
//
// SPDX-License-Identifier: MIT
import './styles.scss';
import React from 'react';
import { RouteComponentProps } from 'react-router';
import { Link, withRouter } from 'react-router-dom';
import Title from 'antd/lib/typography/Title';
import Text from 'antd/lib/typography/Text';
import { withRouter } from 'react-router-dom';
import { Row, Col } from 'antd/lib/grid';
import Layout from 'antd/lib/layout';
import { UserAgreement } from 'reducers';
import SigningLayout, { formSizes } from 'components/signing-common/signing-layout';
import RegisterForm, { RegisterData, UserConfirmation } from './register-form';
interface RegisterPageComponentProps {
@ -23,28 +20,19 @@ interface RegisterPageComponentProps {
firstName: string,
lastName: string,
email: string,
password1: string,
password2: string,
password: string,
confirmations: UserConfirmation[],
) => void;
}
function RegisterPageComponent(props: RegisterPageComponentProps & RouteComponentProps): JSX.Element {
const sizes = {
style: {
width: 400,
},
};
const { fetching, userAgreements, onRegister } = props;
const { Content } = Layout;
return (
<Layout>
<Content>
<Row justify='center' align='middle' style={{ height: '100%' }}>
<Col {...sizes}>
<Title level={2}> Create an account </Title>
<SigningLayout>
<Col {...formSizes.wrapper}>
<Row justify='center'>
<Col {...formSizes.form}>
<RegisterForm
fetching={fetching}
userAgreements={userAgreements}
@ -54,24 +42,15 @@ function RegisterPageComponent(props: RegisterPageComponentProps & RouteComponen
registerData.firstName,
registerData.lastName,
registerData.email,
registerData.password1,
registerData.password2,
registerData.password,
registerData.confirmations,
);
}}
/>
<Row justify='start' align='top'>
<Col>
<Text strong>
Already have an account?
<Link to='/auth/login'> Login </Link>
</Text>
</Col>
</Row>
</Col>
</Row>
</Content>
</Layout>
</Col>
</SigningLayout>
);
}

@ -1,7 +0,0 @@
// Copyright (C) 2020-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
.ant-form-item {
margin-bottom: 12px;
}

@ -1,12 +1,18 @@
// Copyright (C) 2020-2022 Intel Corporation
// Copyright (C) 2022 CVAT.ai Corp
//
// SPDX-License-Identifier: MIT
import React from 'react';
import React, { useCallback } from 'react';
import Form from 'antd/lib/form';
import Button from 'antd/lib/button';
import { MailOutlined } from '@ant-design/icons';
import Input from 'antd/lib/input';
import Icon from '@ant-design/icons';
import Text from 'antd/lib/typography/Text';
import { BackArrowIcon } from 'icons';
import { Col, Row } from 'antd/lib/grid';
import { Link, useLocation } from 'react-router-dom';
import Title from 'antd/lib/typography/Title';
import CVATSigningInput from 'components/signing-common/cvat-signing-input';
export interface ResetPasswordData {
email: string;
@ -18,41 +24,77 @@ interface Props {
}
function ResetPasswordFormComponent({ fetching, onSubmit }: Props): JSX.Element {
const [form] = Form.useForm();
const location = useLocation();
const params = new URLSearchParams(location.search);
const defaultCredential = params.get('credential');
const inputReset = useCallback((name: string):void => {
form.setFieldsValue({ [name]: '' });
}, [form]);
return (
<Form onFinish={onSubmit} className='cvat-reset-password-form'>
<Form.Item
hasFeedback
name='email'
rules={[
{
type: 'email',
message: 'The input is not valid E-mail!',
},
{
required: true,
message: 'Please specify an email address',
},
]}
>
<Input
autoComplete='email'
prefix={<MailOutlined style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Email address'
<div className='cvat-password-reset-form-wrapper'>
<Row justify='space-between' className='cvat-credentials-navigation'>
<Icon
component={() => <Link to='/auth/login'><BackArrowIcon /></Link>}
/>
</Form.Item>
<Form.Item>
<Button
type='primary'
loading={fetching}
disabled={fetching}
htmlType='submit'
className='cvat-reset-password-form-button'
</Row>
<Row>
<Col>
<Title level={2}> Forgot password? </Title>
</Col>
</Row>
<Row>
<Col>
<Title level={2}> Let&apos;s create a new one </Title>
</Col>
</Row>
<Form
form={form}
className='cvat-password-reset-form'
initialValues={{
email: defaultCredential,
}}
onFinish={(resetPasswordData: ResetPasswordData) => {
onSubmit(resetPasswordData);
}}
>
<Form.Item
className='cvat-credentials-form-item'
name='email'
rules={[
{
type: 'email',
message: 'The input is not valid E-mail!',
},
{
required: true,
message: 'Please specify an email address',
},
]}
>
Reset password
</Button>
</Form.Item>
</Form>
<CVATSigningInput
autoComplete='email'
placeholder='enter your email'
prefix='Email'
onReset={() => inputReset('email')}
/>
</Form.Item>
<Row>
<Col className='cvat-password-reset-tip'>
<Text> We will send link to your email </Text>
</Col>
</Row>
<Form.Item>
<Button
className='cvat-credentials-action-button'
loading={fetching}
htmlType='submit'
>
Send
</Button>
</Form.Item>
</Form>
</div>
);
}

@ -4,77 +4,34 @@
// SPDX-License-Identifier: MIT
import React from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import Title from 'antd/lib/typography/Title';
import Text from 'antd/lib/typography/Text';
import { useDispatch, useSelector } from 'react-redux';
import { Row, Col } from 'antd/lib/grid';
import Layout from 'antd/lib/layout';
import { requestPasswordResetAsync } from 'actions/auth-actions';
import { CombinedState } from 'reducers';
import SigningLayout, { formSizes } from 'components/signing-common/signing-layout';
import ResetPasswordForm, { ResetPasswordData } from './reset-password-form';
interface StateToProps {
fetching: boolean;
}
interface DispatchToProps {
onResetPassword: typeof requestPasswordResetAsync;
}
interface ResetPasswordPageComponentProps {
fetching: boolean;
onResetPassword: (email: string) => void;
}
function mapStateToProps(state: CombinedState): StateToProps {
return {
fetching: state.auth.fetching,
};
}
const mapDispatchToProps: DispatchToProps = {
onResetPassword: requestPasswordResetAsync,
};
function ResetPasswordPagePageComponent(props: ResetPasswordPageComponentProps): JSX.Element {
const sizes = {
xs: { span: 14 },
sm: { span: 14 },
md: { span: 10 },
lg: { span: 4 },
xl: { span: 4 },
};
const { fetching, onResetPassword } = props;
const { Content } = Layout;
function ResetPasswordPagePageComponent(): JSX.Element {
const dispatch = useDispatch();
const fetching = useSelector((state: CombinedState) => state.auth.fetching);
return (
<Layout>
<Content>
<Row justify='center' align='middle' style={{ height: '100%' }}>
<Col {...sizes}>
<Title level={2}> Reset password </Title>
<SigningLayout>
<Col {...formSizes.wrapper}>
<Row justify='center'>
<Col {...formSizes.form}>
<ResetPasswordForm
fetching={fetching}
onSubmit={(resetPasswordData: ResetPasswordData): void => {
onResetPassword(resetPasswordData.email);
dispatch(requestPasswordResetAsync(resetPasswordData.email));
}}
/>
<Row justify='start' align='top'>
<Col>
<Text strong>
Go to
<Link to='/auth/login'> login page </Link>
</Text>
</Col>
</Row>
</Col>
</Row>
</Content>
</Layout>
</Col>
</SigningLayout>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(ResetPasswordPagePageComponent);
export default React.memo(ResetPasswordPagePageComponent);

@ -0,0 +1,52 @@
// Copyright (C) 2022 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
import React, { useState } from 'react';
import Icon from '@ant-design/icons';
import Text from 'antd/lib/typography/Text';
import { ClearIcon } from 'icons';
import { Input } from 'antd';
interface SocialAccountLinkProps {
id?: string;
prefix: string;
autoComplete?: string;
placeholder: string;
value?: string;
onReset: () => void;
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
}
function CVATSigningInput(props: SocialAccountLinkProps): JSX.Element {
const {
id, prefix, autoComplete, onReset, placeholder, value, onChange,
} = props;
const [valueNonEmpty, setValueNonEmpty] = useState(false);
return (
<Input
value={value}
autoComplete={autoComplete}
placeholder={placeholder}
prefix={<Text>{prefix}</Text>}
id={id}
suffix={(
valueNonEmpty ? (
<Icon
component={ClearIcon}
onClick={() => {
setValueNonEmpty(false);
onReset();
}}
/>
) : null
)}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
const { value: inputValue } = event.target;
setValueNonEmpty(!!inputValue);
if (onChange) onChange(event);
}}
/>
);
}
export default React.memo(CVATSigningInput);

@ -0,0 +1,83 @@
// Copyright (C) 2022 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
import './styles.scss';
import React from 'react';
import Layout from 'antd/lib/layout';
import { Col, Row } from 'antd/lib/grid';
import { CVATMinimalisticLogo } from 'icons';
import Icon from '@ant-design/icons';
import Title from 'antd/lib/typography/Title';
import SVGSigningBackground from '../../assets/signing-background.svg';
interface SignInLayoutComponentProps {
children: JSX.Element | JSX.Element[];
}
interface Sizes {
xs?: { span: number };
sm?: { span: number };
md?: { span: number };
lg?: { span: number };
xl?: { span: number };
xxl?: { span: number };
}
interface FormSizes {
wrapper: Sizes;
form: Sizes;
}
export const formSizes: FormSizes = {
wrapper: {
xs: { span: 24 },
sm: { span: 24 },
md: { span: 24 },
lg: { span: 24 },
xl: { span: 15 },
xxl: { span: 12 },
},
form: {
xs: { span: 14 },
sm: { span: 14 },
md: { span: 14 },
lg: { span: 14 },
xl: { span: 16 },
xxl: { span: 16 },
},
};
function SignInLayout(props: SignInLayoutComponentProps): JSX.Element {
const { children } = props;
const { Content, Header } = Layout;
const titleSizes = {
xs: { span: 0 },
sm: { span: 0 },
md: { span: 0 },
lg: { span: 0 },
xl: { span: 8 },
xxl: { span: 10 },
};
return (
<Layout>
<SVGSigningBackground className='cvat-signing-background' />
<Header className='cvat-signing-header'>
<Icon className='cvat-logo-icon' component={CVATMinimalisticLogo} />
</Header>
<Layout className='cvat-signing-layout'>
<Content>
<Row justify='center' align='middle' style={{ height: '100%' }}>
<Col {...titleSizes} className='cvat-signing-title'>
<Title>Open Data</Title>
<Title>Annotation Platform</Title>
</Col>
{children}
</Row>
</Content>
</Layout>
</Layout>
);
}
export default SignInLayout;

@ -0,0 +1,43 @@
// Copyright (C) 2022 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
import './styles.scss';
import React from 'react';
import { Col, Row } from 'antd/lib/grid';
import Button from 'antd/lib/button/button';
import Icon from '@ant-design/icons';
import { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon';
interface SocialAccountLinkProps {
children: string;
className?: string;
href: string;
icon: React.ForwardRefExoticComponent<CustomIconComponentProps>;
}
function SocialAccountLink(props: SocialAccountLinkProps): JSX.Element {
const {
children, className, href, icon,
} = props;
return (
<Row>
<Col flex='auto'>
<Button
href={href}
className={`cvat-social-authentication-button ${className}`}
>
<Row align='middle' style={{ width: '100%' }}>
<Col>
<Icon component={icon} />
</Col>
<Col flex='auto'>
{children}
</Col>
</Row>
</Button>
</Col>
</Row>
);
}
export default React.memo(SocialAccountLink);

@ -0,0 +1,338 @@
// Copyright (C) 2022 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
@import '../../styles.scss';
$heading-font: 'Sora', sans-serif;
$signing-font: 'Roboto Flex', sans-serif;
$heading-color: white;
$error-color: #ff4d4f;
$action-button-color-1: black;
$action-button-color-2: gray;
$action-button-color-3: #d4d4d4;
$base-transition: all 0.8s ease;
$social-google-background: #4286f5;
.cvat-signing-layout {
font-family: $signing-font;
}
.cvat-signing-title {
.ant-typography {
color: $heading-color;
margin: 0;
font-size: 68px;
font-family: $heading-font;
font-weight: 400;
}
.ant-typography:last-child {
margin: 0;
}
}
.cvat-signing-header {
background: transparent;
position: absolute;
}
.cvat-signing-background {
position: absolute;
width: 100%;
height: 100%;
min-width: $grid-unit-size*128;
}
.cvat-credentials-link {
a {
color: black;
text-decoration: underline;
text-decoration-thickness: 2px;
}
a:hover {
color: black;
text-decoration: underline;
text-decoration-thickness: 2px;
}
}
.cvat-credentials-form-item {
.ant-input-affix-wrapper {
border: none;
box-shadow: 0 1px 0 0 $border-color-1;
border-radius: 0;
padding: 0;
transition: $base-transition;
}
.ant-input-prefix {
position: absolute;
z-index: 1;
}
.ant-input-affix-wrapper-focused {
box-shadow: 0 2px 0 0 black;
}
.ant-input-affix-wrapper-focused:hover {
box-shadow: 0 2px 0 0 black;
}
.ant-input {
font-size: 20px;
padding: $grid-unit-size * 1.5 0 $grid-unit-size * 0.75 0 !important;
&:-webkit-autofill,
&:-webkit-autofill:hover,
&:-webkit-autofill:focus {
box-shadow: 0 0 0 1000px white inset !important;
transition: background-color 10s ease-in-out 0s;
}
}
.ant-typography {
text-transform: uppercase;
color: $border-color-1;
font-size: 12px;
letter-spacing: 1px;
}
}
.cvat-credentials-form-item.ant-form-item-has-error {
.ant-input-affix-wrapper {
border: none;
box-shadow: 0 1px 0 0 $error-color;
border-radius: 0;
padding: 0;
transition: $base-transition;
}
.ant-input-affix-wrapper-focused {
box-shadow: 0 2px 0 0 $error-color !important;
}
}
.cvat-credentials-navigation {
margin-bottom: $grid-unit-size*4;
}
.cvat-credentials-action-button {
width: 100%;
border-radius: $grid-unit-size*2;
background-color: $action-button-color-1;
height: $grid-unit-size*11;
color: white;
font-size: 16px;
font-weight: bold;
border: none;
box-shadow: none;
transition: $base-transition;
&:hover,
&:focus {
background-color: $action-button-color-1;
color: $action-button-color-2;
}
}
.cvat-login-form-wrapper {
border-radius: $grid-unit-size * 4;
background: $background-color-1;
padding: $grid-unit-size * 6;
height: $grid-unit-size*81;
.cvat-credentials-form-item {
height: $grid-unit-size*9.5;
.ant-form-item-explain {
margin-bottom: $grid-unit-size*0.5;
}
}
.ant-form-item {
margin-bottom: 0;
}
.ant-form-item-explain-error:not(:first-child) {
display: none;
}
}
.cvat-signing-form {
position: relative;
height: $grid-unit-size*52;
.ant-form-item:last-child:not(.cvat-credentials-form-item) {
position: absolute;
bottom: $grid-unit-size*6;
width: 100%;
box-sizing: border-box;
}
}
.cvat-login-form {
@extend .cvat-signing-form;
margin-top: $grid-unit-size*10;
.ant-form-item:first-child {
margin-bottom: $grid-unit-size*15;
}
}
.cvat-login-form-extended {
.ant-form-item:first-child {
margin-bottom: 0;
}
}
.cvat-password-reset-form-wrapper {
@extend .cvat-login-form-wrapper;
h2 {
margin-bottom: 0;
}
.cvat-password-reset-tip {
font-weight: bold;
margin-bottom: $grid-unit-size*20;
}
}
.cvat-password-reset-form {
@extend .cvat-signing-form;
.cvat-credentials-form-item {
margin-bottom: $grid-unit-size*3;
}
margin-top: $grid-unit-size*6;
}
.cvat-register-form-wrapper {
@extend .cvat-login-form-wrapper;
.cvat-register-form {
@extend .cvat-signing-form;
height: $grid-unit-size*68;
}
.cvat-agreements-form-item {
height: $grid-unit-size*5.5;
.ant-form-item-explain {
margin-top: -$grid-unit-size;
}
.ant-checkbox-wrapper {
display: flex;
align-items: center;
}
.ant-checkbox {
top: 0;
.ant-checkbox-inner {
width: $grid-unit-size*5;
height: $grid-unit-size*3;
border-radius: 12px;
background: #d9d9d9;
border: none;
&:hover {
border: none;
}
&::after {
position: absolute;
content: "";
height: $grid-unit-size*2;
width: $grid-unit-size*2;
border-radius: $grid-unit-size;
top: $grid-unit-size*0.5;
left: $grid-unit-size*0.5;
background-color: white;
opacity: 1;
transform: none;
border: none;
}
}
}
.ant-checkbox-checked {
.ant-checkbox-inner {
width: $grid-unit-size*5;
height: $grid-unit-size*3;
border-radius: 12px;
background: $slider-color;
border: none;
&:hover {
border: none;
}
&::after {
position: absolute;
content: "";
height: $grid-unit-size*2;
width: $grid-unit-size*2;
border-radius: $grid-unit-size;
top: $grid-unit-size*0.5;
left: $grid-unit-size*2.5;
background-color: white;
opacity: 1;
transform: none;
border: none;
}
}
}
}
.cvat-register-form-last-field {
margin-bottom: 0;
}
}
.cvat-register-form-wrapper-extended {
@extend .cvat-login-form-wrapper;
height: $grid-unit-size*88;
.cvat-register-form-last-field {
margin-bottom: $grid-unit-size;
}
.cvat-register-form {
@extend .cvat-signing-form;
height: $grid-unit-size*76;
}
}
.cvat-social-authentication-button {
@extend .cvat-credentials-action-button;
.anticon {
margin-top: $grid-unit-size*0.5;
}
letter-spacing: 1px;
margin-bottom: $grid-unit-size;
padding: $grid-unit-size $grid-unit-size*4;
display: flex;
}
.cvat-social-authentication-google {
background: $social-google-background;
&:hover,
&:focus {
background: $social-google-background;
color: $action-button-color-3;
}
}

@ -20,8 +20,7 @@ interface DispatchToProps {
firstName: string,
lastName: string,
email: string,
password1: string,
password2: string,
password: string,
userAgreement: UserConfirmation[],
) => void;
}

@ -6,6 +6,7 @@
import React from 'react';
import SVGCVATLogo from './assets/cvat-logo.svg';
import SVGCVATMinimalisticLogo from './assets/cvat-minimalistic-logo.svg';
import SVGCursorIcon from './assets/cursor-icon.svg';
import SVGMoveIcon from './assets/move-icon.svg';
import SVGRotateIcon from './assets/rotate-icon.svg';
@ -58,10 +59,16 @@ import SVGEraserIcon from './assets/eraser-icon.svg';
import SVGPolygonPlusIcon from './assets/polygon-plus.svg';
import SVGPolygonMinusIcon from './assets/polygon-minus.svg';
import SVGMultiPlusIcon from './assets/multi-plus-icon.svg';
import SVGBackArrowIcon from './assets/back-arrow-icon.svg';
import SVGClearIcon from './assets/clear-icon.svg';
import SVGShowPasswordIcon from './assets/show-password.svg';
import SVGSocialGithubLogo from './assets/social-github-logo.svg';
import SVGSocialGoogleLogo from './assets/social-google-logo.svg';
import SVGPlusIcon from './assets/plus-icon.svg';
import SVGCheckIcon from './assets/check-icon.svg';
export const CVATLogo = React.memo((): JSX.Element => <SVGCVATLogo />);
export const CVATMinimalisticLogo = React.memo((): JSX.Element => <SVGCVATMinimalisticLogo />);
export const CursorIcon = React.memo((): JSX.Element => <SVGCursorIcon />);
export const MoveIcon = React.memo((): JSX.Element => <SVGMoveIcon />);
export const RotateIcon = React.memo((): JSX.Element => <SVGRotateIcon />);
@ -114,5 +121,10 @@ export const EraserIcon = React.memo((): JSX.Element => <SVGEraserIcon />);
export const PolygonPlusIcon = React.memo((): JSX.Element => <SVGPolygonPlusIcon />);
export const PolygonMinusIcon = React.memo((): JSX.Element => <SVGPolygonMinusIcon />);
export const MutliPlusIcon = React.memo((): JSX.Element => <SVGMultiPlusIcon />);
export const BackArrowIcon = React.memo((): JSX.Element => <SVGBackArrowIcon />);
export const ClearIcon = React.memo((): JSX.Element => <SVGClearIcon />);
export const ShowPasswordIcon = React.memo((): JSX.Element => <SVGShowPasswordIcon />);
export const SocialGithubLogo = React.memo((): JSX.Element => <SVGSocialGithubLogo />);
export const SocialGoogleLogo = React.memo((): JSX.Element => <SVGSocialGoogleLogo />);
export const PlusIcon = React.memo((): JSX.Element => <SVGPlusIcon />);
export const CheckIcon = React.memo((): JSX.Element => <SVGCheckIcon />);

@ -15,9 +15,11 @@
content="Computer Vision Annotation Tool (CVAT) is a free, open source, web-based image and video annotation tool which is used for labeling data for computer vision algorithms. CVAT supports the primary tasks of supervised machine learning: object detection, image classification, and image segmentation. CVAT allows users to annotate data for each of these cases"
/>
<meta name="”robots”" content="index, follow" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto+Flex:opsz,wght@8..144,400;8..144,500;8..144,600;8..144,700;8..144,800&family=Sora&display=swap" rel="stylesheet"> </head>
<title>Computer Vision Annotation Tool</title>
</head>
<body>
<div id="root"></div>
<div id="layout-grid"></div>

@ -29,8 +29,8 @@ const validationPatterns = {
},
validateUsernameCharacters: {
pattern: /^[a-zA-Z0-9_-]{5,}$/,
message: 'Only characters (a-z), (A-Z), (0-9), -, _ are available',
pattern: /^[a-zA-Z0-9_\-.]{5,}$/,
message: 'Only characters (a-z), (A-Z), (0-9), -, _, . are available',
},
/*

@ -16,5 +16,5 @@
"jsx": "preserve",
"baseUrl": "src"
},
"include": ["./index.d.ts", "src/index.tsx", "src"]
"include": ["./index.d.ts", "src/index.tsx", "src/assets/index.d.ts", "src"]
}

@ -19,7 +19,7 @@ context('Reset password notification.', () => {
it('Sending a password reset request', () => {
cy.get('#email').type(dummyEmail);
cy.get('.cvat-reset-password-form-button').click();
cy.get('.cvat-credentials-action-button').click();
cy.contains('Check your email').should('be.visible');
});
});

@ -11,8 +11,8 @@ context('When clicking on the Logout button, get the user session closed.', () =
let taskId;
function login(credential, password) {
cy.get('[placeholder="Email or Username"]').clear().type(credential);
cy.get('[placeholder="Password"]').clear().type(password);
cy.get('[placeholder="enter your email or username"]').clear().type(credential);
cy.get('[placeholder="enter your password"]').clear().type(password);
cy.get('[type="submit"]').click();
}

@ -25,27 +25,23 @@ context('Issue 1599 (Chinese alphabet).', () => {
describe('User registration using the Chinese alphabet.', () => {
it('Filling in the placeholder "First name"', () => {
cy.get('[placeholder="First name"]').type(firstName).should('not.have.class', 'has-error');
cy.get('[placeholder="enter your first name"]').type(firstName).should('not.have.class', 'has-error');
});
it('Filling in the placeholder "Last name"', () => {
cy.get('[placeholder="Last name"]').type(lastName).should('not.have.class', 'has-error');
cy.get('[placeholder="enter your last name"]').type(lastName).should('not.have.class', 'has-error');
});
it('Filling in the placeholder "Username"', () => {
cy.get('[placeholder="Username"]').type(userName);
cy.get('[placeholder="enter your username"]').type(userName);
});
it('Filling in the placeholder "Email address"', () => {
cy.get('[placeholder="Email address"]').type(email);
cy.get('[placeholder="enter your email"]').type(email);
});
it('Filling in the placeholder "Password"', () => {
cy.get('[placeholder="Password"]').type(password);
});
it('Filling in the placeholder "Confirm password"', () => {
cy.get('[placeholder="Confirm password"]').type(password);
cy.get('[placeholder="enter your password"]').type(password);
});
it('Click to "Submit" button', () => {

@ -25,27 +25,23 @@ context('Issue 1599 (Polish alphabet).', () => {
describe('User registration using the Polish alphabet.', () => {
it('Filling in the placeholder "First name"', () => {
cy.get('[placeholder="First name"]').type(firstName).should('not.have.class', 'has-error');
cy.get('[placeholder="enter your first name"]').type(firstName).should('not.have.class', 'has-error');
});
it('Filling in the placeholder "Last name"', () => {
cy.get('[placeholder="Last name"]').type(lastName).should('not.have.class', 'has-error');
cy.get('[placeholder="enter your last name"]').type(lastName).should('not.have.class', 'has-error');
});
it('Filling in the placeholder "Username"', () => {
cy.get('[placeholder="Username"]').type(userName);
cy.get('[placeholder="enter your username"]').type(userName);
});
it('Filling in the placeholder "Email address"', () => {
cy.get('[placeholder="Email address"]').type(email);
cy.get('[placeholder="enter your email"]').type(email);
});
it('Filling in the placeholder "Password"', () => {
cy.get('[placeholder="Password"]').type(password);
});
it('Filling in the placeholder "Confirm password"', () => {
cy.get('[placeholder="Confirm password"]').type(password);
cy.get('[placeholder="enter your password"]').type(password);
});
it('Click to "Submit" button', () => {

@ -20,9 +20,9 @@ require('cy-verify-downloads').addCustomCommand();
let selectedValueGlobal = '';
Cypress.Commands.add('login', (username = Cypress.env('user'), password = Cypress.env('password'), page = 'tasks') => {
cy.get('[placeholder="Email or Username"]').type(username);
cy.get('[placeholder="Password"]').type(password);
cy.get('[type="submit"]').click();
cy.get('[placeholder="enter your email or username"]').type(username);
cy.get('[placeholder="enter your password"]').type(password);
cy.get('.cvat-credentials-action-button').click();
cy.url().should('contain', `/${page}`);
cy.document().then((doc) => {
const loadSettingFailNotice = Array.from(doc.querySelectorAll('.cvat-notification-notice-load-settings-fail'));
@ -40,7 +40,7 @@ Cypress.Commands.add('logout', (username = Cypress.env('user')) => {
cy.url().should('include', '/auth/login');
cy.visit('/auth/login');
cy.url().should('not.include', '?next=');
cy.contains('Login').should('exist');
cy.contains('Sign in').should('exist');
});
Cypress.Commands.add('userRegistration', (firstName, lastName, userName, emailAddr, password) => {
@ -48,9 +48,8 @@ Cypress.Commands.add('userRegistration', (firstName, lastName, userName, emailAd
cy.get('#lastName').type(lastName);
cy.get('#username').type(userName);
cy.get('#email').type(emailAddr);
cy.get('#password1').type(password);
cy.get('#password2').type(password);
cy.get('.register-form-button').click();
cy.get('#password').type(password);
cy.get('.cvat-credentials-action-button').click();
if (Cypress.browser.family === 'chromium') {
cy.url().should('include', '/tasks');
}

@ -4103,7 +4103,7 @@ custom-error-instance@2.1.1:
svg.select.js "3.0.1"
"cvat-core@link:./cvat-core":
version "7.0.1"
version "7.2.0"
dependencies:
axios "^0.27.2"
browser-or-node "^2.0.0"

Loading…
Cancel
Save