From 7706eee504e73d538dd9d7ff76e7b9ae6aaaf7cb Mon Sep 17 00:00:00 2001 From: Kirill Lakhov Date: Tue, 15 Nov 2022 17:12:04 +0300 Subject: [PATCH] Improved UX of signing pages (#5295) --- CHANGELOG.md | 2 +- cvat-ui/package.json | 2 +- cvat-ui/src/assets/cvat-minimalistic-logo.svg | 2 +- cvat-ui/src/components/header/styles.scss | 12 ++-- .../src/components/login-page/login-form.tsx | 39 ++++++------ .../register-page/register-form.tsx | 41 ++++++------- .../reset-password-form.tsx | 10 +--- .../signing-common/cvat-signing-input.tsx | 59 ++++++++++++------- .../signing-common/signing-layout.tsx | 14 ++++- .../src/components/signing-common/styles.scss | 51 ++++++++++++---- .../actions_users/issue_1810_login_logout.js | 4 +- .../issue_1599_ch_user_registration.js | 10 ++-- .../issue_1599_pl_user_registration.js | 10 ++-- tests/cypress/support/commands.js | 6 +- 14 files changed, 151 insertions(+), 111 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f172112b..4d7a8ce0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) () - Added Webhooks () -- Authentication with social accounts google & github (, ) +- Authentication with social accounts google & github (, , ) - REST API tests to export job datasets & annotations and validate their structure () ### Changed diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 98474315..fa7b7eee 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.44.0", + "version": "1.44.1", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/assets/cvat-minimalistic-logo.svg b/cvat-ui/src/assets/cvat-minimalistic-logo.svg index c8a10ba9..790c859d 100644 --- a/cvat-ui/src/assets/cvat-minimalistic-logo.svg +++ b/cvat-ui/src/assets/cvat-minimalistic-logo.svg @@ -1,4 +1,4 @@ - + diff --git a/cvat-ui/src/components/header/styles.scss b/cvat-ui/src/components/header/styles.scss index de0f84e7..fe5ff8f6 100644 --- a/cvat-ui/src/components/header/styles.scss +++ b/cvat-ui/src/components/header/styles.scss @@ -29,6 +29,12 @@ } } + .anticon.cvat-logo-icon { + > svg { + transform: scale(0.7); + } + } + width: 50%; display: flex; justify-content: flex-start; @@ -49,12 +55,6 @@ } } -.anticon.cvat-logo-icon { - > svg { - transform: scale(0.7); - } -} - .cvat-header-menu-user-dropdown { display: flex; align-items: center; diff --git a/cvat-ui/src/components/login-page/login-form.tsx b/cvat-ui/src/components/login-page/login-form.tsx index 7a05fae5..20028b6d 100644 --- a/cvat-ui/src/components/login-page/login-form.tsx +++ b/cvat-ui/src/components/login-page/login-form.tsx @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: MIT -import React, { useCallback, useState } from 'react'; +import React, { useState } from 'react'; import Form from 'antd/lib/form'; import Button from 'antd/lib/button'; import Input from 'antd/lib/input'; @@ -15,6 +15,7 @@ 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'; +import CVATSigningInput, { CVATInputType } from 'components/signing-common/cvat-signing-input'; export interface LoginData { credential: string; @@ -35,9 +36,6 @@ function LoginFormComponent(props: Props): JSX.Element { const [form] = Form.useForm(); const [credential, setCredential] = useState(''); - const inputReset = useCallback((name: string):void => { - form.setFieldsValue({ [name]: '' }); - }, [form]); const forgotPasswordLink = ( @@ -97,23 +95,21 @@ function LoginFormComponent(props: Props): JSX.Element { > } - suffix={( - credential && ( - { - setCredential(''); - inputReset('credential'); - }} - /> - ) + className={credential ? 'cvat-input-floating-label-above' : 'cvat-input-floating-label'} + suffix={credential && ( + { + setCredential(''); + form.setFieldsValue({ credential: '', password: '' }); + }} + /> )} onChange={(event) => { const { value } = event.target; setCredential(value); - if (!value) inputReset('credential'); + if (!value) form.setFieldsValue({ credential: '', password: '' }); }} /> @@ -129,12 +125,11 @@ function LoginFormComponent(props: Props): JSX.Element { }, ]} > - Password - } + ) diff --git a/cvat-ui/src/components/register-page/register-form.tsx b/cvat-ui/src/components/register-page/register-form.tsx index fedcaa28..70ee1923 100644 --- a/cvat-ui/src/components/register-page/register-form.tsx +++ b/cvat-ui/src/components/register-page/register-form.tsx @@ -3,12 +3,10 @@ // // SPDX-License-Identifier: MIT -import React, { useCallback, useState } from 'react'; +import React, { 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'; @@ -17,7 +15,7 @@ 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'; +import CVATSigningInput, { CVATInputType } from 'components/signing-common/cvat-signing-input'; export interface UserConfirmation { name: string; @@ -104,9 +102,6 @@ function RegisterFormComponent(props: Props): JSX.Element { 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 (
@@ -146,9 +141,9 @@ function RegisterFormComponent(props: Props): JSX.Element { > inputReset('firstName')} + placeholder='First name' + autoComplete='given-name' + onReset={() => form.setFieldsValue({ firstName: '' })} /> @@ -166,9 +161,9 @@ function RegisterFormComponent(props: Props): JSX.Element { > inputReset('lastName')} + placeholder='Last name' + autoComplete='family-name' + onReset={() => form.setFieldsValue({ lastName: '' })} /> @@ -190,9 +185,8 @@ function RegisterFormComponent(props: Props): JSX.Element { inputReset('email')} + placeholder='Email' + onReset={() => form.setFieldsValue({ email: '', username: '' })} onChange={(event) => { const { value } = event.target; if (!usernameEdited) { @@ -217,9 +211,9 @@ function RegisterFormComponent(props: Props): JSX.Element { > inputReset('username')} + placeholder='Username' + autoComplete='username' + onReset={() => form.setFieldsValue({ username: '' })} onChange={() => setUsernameEdited(true)} /> @@ -233,12 +227,13 @@ function RegisterFormComponent(props: Props): JSX.Element { }, validatePassword, ]} > - Password} + - {userAgreements.map((userAgreement: UserAgreement): JSX.Element => ( { - form.setFieldsValue({ [name]: '' }); - }, [form]); return (
@@ -74,9 +71,8 @@ function ResetPasswordFormComponent({ fetching, onSubmit }: Props): JSX.Element > inputReset('email')} + placeholder='Email' + onReset={() => form.setFieldsValue({ email: '' })} /> diff --git a/cvat-ui/src/components/signing-common/cvat-signing-input.tsx b/cvat-ui/src/components/signing-common/cvat-signing-input.tsx index 16623a79..83453ece 100644 --- a/cvat-ui/src/components/signing-common/cvat-signing-input.tsx +++ b/cvat-ui/src/components/signing-common/cvat-signing-input.tsx @@ -1,50 +1,65 @@ // Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import Icon from '@ant-design/icons'; -import Text from 'antd/lib/typography/Text'; import { ClearIcon } from 'icons'; import { Input } from 'antd'; +import Text from 'antd/lib/typography/Text'; interface SocialAccountLinkProps { id?: string; - prefix: string; autoComplete?: string; placeholder: string; value?: string; - onReset: () => void; + type?: CVATInputType; + onReset?: () => void; onChange?: (event: React.ChangeEvent) => void; } +export enum CVATInputType { + TEXT = 'text', + PASSWORD = 'passord', +} + function CVATSigningInput(props: SocialAccountLinkProps): JSX.Element { const { - id, prefix, autoComplete, onReset, placeholder, value, onChange, + id, autoComplete, type, onReset, placeholder, value, onChange, } = props; const [valueNonEmpty, setValueNonEmpty] = useState(false); + useEffect((): void => { + setValueNonEmpty(!!value); + }, [value]); + + if (type === CVATInputType.PASSWORD) { + return ( + {placeholder}} + id={id} + onChange={onChange} + /> + ); + } return ( {prefix}} + className={valueNonEmpty ? 'cvat-input-floating-label-above' : 'cvat-input-floating-label'} + prefix={{placeholder}} id={id} - suffix={( - valueNonEmpty ? ( - { - setValueNonEmpty(false); - onReset(); - }} - /> - ) : null + suffix={valueNonEmpty && ( + { + setValueNonEmpty(false); + if (onReset) onReset(); + }} + /> )} - onChange={(event: React.ChangeEvent) => { - const { value: inputValue } = event.target; - setValueNonEmpty(!!inputValue); - if (onChange) onChange(event); - }} + onChange={onChange} /> ); } diff --git a/cvat-ui/src/components/signing-common/signing-layout.tsx b/cvat-ui/src/components/signing-common/signing-layout.tsx index 7520a788..4edc44de 100644 --- a/cvat-ui/src/components/signing-common/signing-layout.tsx +++ b/cvat-ui/src/components/signing-common/signing-layout.tsx @@ -59,11 +59,23 @@ function SignInLayout(props: SignInLayoutComponentProps): JSX.Element { xl: { span: 8 }, xxl: { span: 10 }, }; + const logoSizes = { + xs: { span: 21 }, + sm: { span: 21 }, + md: { span: 21 }, + lg: { span: 21 }, + xl: { span: 21 }, + xxl: { span: 22 }, + }; return (
- + + + + +
diff --git a/cvat-ui/src/components/signing-common/styles.scss b/cvat-ui/src/components/signing-common/styles.scss index bf7240d3..1f0bc313 100644 --- a/cvat-ui/src/components/signing-common/styles.scss +++ b/cvat-ui/src/components/signing-common/styles.scss @@ -11,8 +11,10 @@ $error-color: #ff4d4f; $action-button-color-1: black; $action-button-color-2: gray; $action-button-color-3: #d4d4d4; +$placeholder-color: #c5bfbf; $base-transition: all 0.8s ease; +$input-transition: all 0.3s ease; $social-google-background: #4286f5; @@ -36,7 +38,13 @@ $social-google-background: #4286f5; .cvat-signing-header { background: transparent; - position: absolute; + position: fixed; + padding: $grid-unit-size*2 0 0 0; + width: 100%; + + svg { + transform: none; + } } .cvat-signing-background { @@ -60,6 +68,31 @@ $social-google-background: #4286f5; } } +.cvat-input-floating-label { + .ant-input-prefix { + position: absolute; + top: 50%; + transform: translateY(-50%); + z-index: 1; + font-size: 20px; + transition: $input-transition; + transform-origin: left top; + + .ant-typography { + color: $placeholder-color; + } + } +} + +.cvat-input-floating-label-above { + @extend .cvat-input-floating-label; + + .ant-input-prefix { + transition: $input-transition; + transform: translateY(-100%) scale(0.7); + } +} + .cvat-credentials-form-item { .ant-input-affix-wrapper { border: none; @@ -69,12 +102,9 @@ $social-google-background: #4286f5; transition: $base-transition; } - .ant-input-prefix { - position: absolute; - z-index: 1; - } - .ant-input-affix-wrapper-focused { + @extend .cvat-input-floating-label-above; + box-shadow: 0 2px 0 0 black; } @@ -94,11 +124,8 @@ $social-google-background: #4286f5; } } - .ant-typography { - text-transform: uppercase; - color: $border-color-1; - font-size: 12px; - letter-spacing: 1px; + .cvat-signing-input-not-empty { + @extend .cvat-input-floating-label; } } @@ -322,8 +349,8 @@ $social-google-background: #4286f5; } letter-spacing: 1px; + padding: 0 $grid-unit-size*4 !important; margin-bottom: $grid-unit-size; - padding: $grid-unit-size $grid-unit-size*4; display: flex; } diff --git a/tests/cypress/integration/actions_users/issue_1810_login_logout.js b/tests/cypress/integration/actions_users/issue_1810_login_logout.js index 8cd764b8..994fa6f4 100644 --- a/tests/cypress/integration/actions_users/issue_1810_login_logout.js +++ b/tests/cypress/integration/actions_users/issue_1810_login_logout.js @@ -12,8 +12,8 @@ context('When clicking on the Logout button, get the user session closed.', () = let taskId; function login(credential, password) { - cy.get('[placeholder="enter your email or username"]').clear().type(credential); - cy.get('[placeholder="enter your password"]').clear().type(password); + cy.get('#credential').clear().type(credential); + cy.get('#password').clear().type(password); cy.get('[type="submit"]').click(); } diff --git a/tests/cypress/integration/actions_users/registration_involved/issue_1599_ch_user_registration.js b/tests/cypress/integration/actions_users/registration_involved/issue_1599_ch_user_registration.js index c9dfdbb7..9161ebec 100644 --- a/tests/cypress/integration/actions_users/registration_involved/issue_1599_ch_user_registration.js +++ b/tests/cypress/integration/actions_users/registration_involved/issue_1599_ch_user_registration.js @@ -25,23 +25,23 @@ context('Issue 1599 (Chinese alphabet).', () => { describe('User registration using the Chinese alphabet.', () => { it('Filling in the placeholder "First name"', () => { - cy.get('[placeholder="enter your first name"]').type(firstName).should('not.have.class', 'has-error'); + cy.get('#firstName').type(firstName).should('not.have.class', 'has-error'); }); it('Filling in the placeholder "Last name"', () => { - cy.get('[placeholder="enter your last name"]').type(lastName).should('not.have.class', 'has-error'); + cy.get('#lastName').type(lastName).should('not.have.class', 'has-error'); }); it('Filling in the placeholder "Username"', () => { - cy.get('[placeholder="enter your username"]').type(userName); + cy.get('#username').type(userName); }); it('Filling in the placeholder "Email address"', () => { - cy.get('[placeholder="enter your email"]').type(email); + cy.get('#email').type(email); }); it('Filling in the placeholder "Password"', () => { - cy.get('[placeholder="enter your password"]').type(password); + cy.get('#password1').type(password); }); it('Click to "Submit" button', () => { diff --git a/tests/cypress/integration/actions_users/registration_involved/issue_1599_pl_user_registration.js b/tests/cypress/integration/actions_users/registration_involved/issue_1599_pl_user_registration.js index 70e5a778..03d4d9fb 100644 --- a/tests/cypress/integration/actions_users/registration_involved/issue_1599_pl_user_registration.js +++ b/tests/cypress/integration/actions_users/registration_involved/issue_1599_pl_user_registration.js @@ -25,23 +25,23 @@ context('Issue 1599 (Polish alphabet).', () => { describe('User registration using the Polish alphabet.', () => { it('Filling in the placeholder "First name"', () => { - cy.get('[placeholder="enter your first name"]').type(firstName).should('not.have.class', 'has-error'); + cy.get('#firstName').type(firstName).should('not.have.class', 'has-error'); }); it('Filling in the placeholder "Last name"', () => { - cy.get('[placeholder="enter your last name"]').type(lastName).should('not.have.class', 'has-error'); + cy.get('#lastName').type(lastName).should('not.have.class', 'has-error'); }); it('Filling in the placeholder "Username"', () => { - cy.get('[placeholder="enter your username"]').type(userName); + cy.get('#username').type(userName); }); it('Filling in the placeholder "Email address"', () => { - cy.get('[placeholder="enter your email"]').type(email); + cy.get('#email').type(email); }); it('Filling in the placeholder "Password"', () => { - cy.get('[placeholder="enter your password"]').type(password); + cy.get('#password1').type(password); }); it('Click to "Submit" button', () => { diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js index e53016be..047f5af3 100644 --- a/tests/cypress/support/commands.js +++ b/tests/cypress/support/commands.js @@ -20,8 +20,8 @@ require('cy-verify-downloads').addCustomCommand(); let selectedValueGlobal = ''; Cypress.Commands.add('login', (username = Cypress.env('user'), password = Cypress.env('password'), page = 'tasks') => { - cy.get('[placeholder="enter your email or username"]').type(username); - cy.get('[placeholder="enter your password"]').type(password); + cy.get('#credential').type(username); + cy.get('#password').type(password); cy.get('.cvat-credentials-action-button').click(); cy.url().should('contain', `/${page}`); cy.document().then((doc) => { @@ -48,7 +48,7 @@ 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('#password').type(password); + cy.get('#password1').type(password); cy.get('.cvat-credentials-action-button').click(); if (Cypress.browser.family === 'chromium') { cy.url().should('include', '/tasks');