[MM-62986][MM-63011][MM-63013][MM-63014][MM-63018] Various accessibility fixes around login, account creation and MFA setup (#30847)

* [MM-62986] Ensure focus goes back to the inputs after an error for Login/Create Account/MFA

* [MM-63011] Show outline on Mattermost logo link when focused

* [MM-63018] Remove tabindex from the login/signup cards and use <form> element for submit

* [MM-63014] Toggle aria-label when show/hide password is pressed

* [MM-63013] Mention the field name when showing an error message about the password field

* Fix lint

* Update screenshots and fix tests

* Update screenshots and fix tests

* Fix tests

* Update webapp/channels/src/components/mfa/setup/setup.tsx

Co-authored-by: Harrison Healey <harrisonmhealey@gmail.com>

* update screenshots

---------

Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>
Co-authored-by: Harrison Healey <harrisonmhealey@gmail.com>
Этот коммит содержится в:
Devin Binnie
2025-05-07 15:19:33 -04:00
коммит произвёл GitHub
родитель 92011a6c75
Коммит ee61301b67
36 изменённых файлов: 262 добавлений и 89 удалений

Просмотреть файл

@@ -80,8 +80,8 @@ describe('Profile', () => {
// # Save
cy.uiSave();
// * Verify for error message: "Must be 5-72 characters long."
cy.get('#clientError').should('be.visible').should('have.text', 'Must be 5-72 characters long.');
// * Verify for error message: "Your password must be 5-72 characters long."
cy.get('#clientError').should('be.visible').should('have.text', 'Your password must be 5-72 characters long.');
});
it('MM-T2084 Password: Cancel out of password changes causes no changes to be made', () => {

Просмотреть файл

@@ -49,7 +49,7 @@ describe('Authentication', () => {
cy.uiSave();
cy.findByText('Must be 7-72 characters long.').should('be.visible');
cy.findByText('Your password must be 7-72 characters long.').should('be.visible');
cy.apiLogout();
@@ -65,7 +65,7 @@ describe('Authentication', () => {
cy.findByText('Create Account').click();
// * Assert the error is what is expected;
cy.findByText('Must be 7-72 characters long.').should('be.visible');
cy.findByText('Your password must be 7-72 characters long.').should('be.visible');
cy.get('#input_password-input').clear().type('greaterthan7');
@@ -118,7 +118,7 @@ describe('Authentication', () => {
cy.findByText('Create Account').click();
// * Assert the error is what is expected;
cy.findByText('Must be 5-72 characters long and include both lowercase and uppercase letters, numbers, and special characters.').should('be.visible');
cy.findByText('Your password must be 5-72 characters long and include both lowercase and uppercase letters, numbers, and special characters.').should('be.visible');
});
});

Просмотреть файл

@@ -58,7 +58,7 @@ describe('Authentication', () => {
cy.visit('/login');
// # Remove autofocus from login input
cy.get('.login-body-card-content').should('be.visible').focus();
cy.get('.login-body-card-title').click();
// # Clear email/username field and type username
cy.apiGetClientLicense().then(({isLicensed}) => {

Просмотреть файл

@@ -28,8 +28,8 @@ describe('Managing bot accounts', () => {
cy.apiLogout();
cy.visit('/login');
// # Remove autofocus from login input
cy.get('.login-body-card-content').should('be.visible').focus();
// # Click on card titile to remove auto focus from the email field
cy.get('.login-body-card-title').click();
// # Enter bot name in the email field
cy.findByPlaceholderText('Email, Username or AD/LDAP Username', {timeout: TIMEOUTS.ONE_MIN}).clear().type(botName);

Просмотреть файл

@@ -22,7 +22,7 @@ describe('SignIn Authentication', () => {
cy.visit('/login');
// # Remove autofocus from login input
cy.get('.login-body-card-content').should('be.visible').focus();
cy.get('.login-body-card-title').click();
});
});
@@ -48,7 +48,7 @@ describe('SignIn Authentication', () => {
cy.url().should('include', '/login');
// # Remove autofocus from login input
cy.get('.login-body-card-content').should('be.visible').focus();
cy.get('.login-body-card-title').click();
// # Enter actual user's username in the email field
cy.findByPlaceholderText(loginPlaceholder).clear().type(testUser.username);

Просмотреть файл

@@ -46,8 +46,8 @@ describe('Login page with open server', () => {
// * Verify title of the document is correct
cy.title().should('include', config.TeamSettings.SiteName);
// # Remove autofocus from login id input
cy.get('.login-body-card-content').should('be.visible').focus();
// # Remove autofocus from login input
cy.get('.login-body-card-title').click();
// * Verify email/username field is present
cy.findByPlaceholderText('Email or Username').should('exist').and('be.visible');
@@ -156,6 +156,9 @@ describe('Login page with open server', () => {
it('MM-T3306_7 Should show error with invalid password', () => {
const invalidPassword = `${Date.now()}-password`;
// Remove autofocus from login input
cy.get('.login-body-card-title').click();
// # Lets verify generated password is not an actual password
expect(invalidPassword).to.not.equal(testUser.password);
@@ -173,6 +176,9 @@ describe('Login page with open server', () => {
});
it('MM-T3306_8 Should login with a valid email and password and logout', () => {
// Remove autofocus from login input
cy.get('.login-body-card-title').click();
// # Enter actual users email/username in the email field
cy.findByPlaceholderText('Email or Username').clear().type(testUser.username);
@@ -196,8 +202,8 @@ describe('Login page with open server', () => {
// # Visit login page
cy.visit('/login');
// # Remove autofocus from login id input
cy.get('.login-body-card-content').should('be.visible').focus();
// # Remove autofocus from login input
cy.get('.login-body-card-title').click();
// # Enter actual users email/username in the email field
cy.findByPlaceholderText('Email or Username').clear().type(testUser.username);

Просмотреть файл

@@ -75,7 +75,7 @@ describe('Signup Email page', () => {
cy.findByText('You can use lowercase letters, numbers, periods, dashes, and underscores.').should('be.visible');
cy.get('#input_password-input').should('be.visible').and('have.attr', 'placeholder', 'Choose a Password');
cy.findByText('Must be 5-72 characters long.').should('be.visible');
cy.findByText('Your password must be 5-72 characters long.').should('be.visible');
cy.get('#saveSetting').scrollIntoView().should('be.visible');
cy.get('#saveSetting').should('contain', 'Create Account');

Просмотреть файл

@@ -164,7 +164,7 @@ describe('System Console > User Management > Users', () => {
// * Verify the appropriate error is returned.
cy.get('form.form-horizontal').find('.has-error p.error').should('be.visible').
and('contain', 'Must be 5-72 characters long.');
and('contain', 'Your password must be 5-72 characters long.');
});
it('MM-T936 Users - System admin changes own password - Blank fields', () => {
@@ -193,7 +193,7 @@ describe('System Console > User Management > Users', () => {
// * Verify the appropriate error is returned.
cy.get('form.form-horizontal').find('.has-error p.error').should('be.visible').
and('contain', 'Must be 5-72 characters long.');
and('contain', 'Your password must be 5-72 characters long.');
});
it('MM-T937 Users - System admin changes own password - Successfully changed', () => {

Просмотреть файл

@@ -5,7 +5,7 @@ import * as TIMEOUTS from '../fixtures/timeouts';
Cypress.Commands.add('checkLoginPage', (settings = {}) => {
// # Remove autofocus from login input
cy.get('.login-body-card-content').should('be.visible').focus();
cy.get('.login-body-card-title').click();
// * Check elements in the body
cy.get('#input_loginId', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').and(($loginTextbox) => {

Просмотреть файл

@@ -37,7 +37,7 @@ export default class LoginPage {
this.loginPlaceholder = page.locator(`[placeholder="Email or Username"]`);
this.loginWithAdLdapPlaceholder = page.locator(`[placeholder="Email, Username or AD/LDAP Username"]`);
this.passwordInput = page.locator('#input_password-input');
this.passwordToggleButton = page.getByRole('button', {name: 'Show or hide password'});
this.passwordToggleButton = page.locator('#password_toggle');
this.signInButton = page.locator('button:has-text("Log in")');
this.createAccountLink = page.locator("text=Don't have an account?");
this.forgotPasswordLink = page.locator('text=Forgot your password?');

Просмотреть файл

@@ -40,7 +40,7 @@ export default class SignupPage {
this.emailInput = page.locator('#input_email');
this.usernameInput = page.locator('#input_name');
this.passwordInput = page.locator('#input_password-input');
this.passwordToggleButton = page.getByRole('button', {name: 'Show or hide password'});
this.passwordToggleButton = page.locator('#password_toggle');
this.createAccountButton = page.locator('button:has-text("Create Account")');
this.emailError = page.locator('text=Please enter a valid email address');
this.usernameError = page.locator(

Просмотреть файл

@@ -67,10 +67,6 @@ test('/login accessibility tab support', async ({pw}) => {
// * Should move focus to login body after shift+tab
await pw.loginPage.loginInput.press('Shift+Tab');
expect(await pw.loginPage.bodyCard).toBeFocused();
// * Should move focus to create account link after shift+tab
await pw.loginPage.bodyCard.press('Shift+Tab');
expect(await pw.loginPage.createAccountLink).toBeFocused();
// * Should move focus to login body after tab

Просмотреть файл

@@ -79,14 +79,6 @@ test('/signup_user_complete accessibility tab support', async ({pw}, testInfo) =
await pw.signupPage.emailInput.focus();
expect(await pw.signupPage.emailInput).toBeFocused();
// * Should move focus to sign up body after shift+tab
await pw.signupPage.emailInput.press('Shift+Tab');
expect(await pw.signupPage.bodyCard).toBeFocused();
// * Should move focus to sign up body after shift+tab
await pw.signupPage.emailInput.press('Shift+Tab');
expect(await pw.signupPage.bodyCard).toBeFocused();
if (testInfo.project.name === 'ipad') {
// * Should move focus to header back button after shift+tab
await pw.signupPage.bodyCard.press('Shift+Tab');

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 186 KiB

После

Ширина:  |  Высота:  |  Размер: 185 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 488 KiB

После

Ширина:  |  Высота:  |  Размер: 489 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 124 KiB

После

Ширина:  |  Высота:  |  Размер: 138 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 211 KiB

После

Ширина:  |  Высота:  |  Размер: 208 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 219 KiB

После

Ширина:  |  Высота:  |  Размер: 210 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 507 KiB

После

Ширина:  |  Высота:  |  Размер: 508 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 176 KiB

После

Ширина:  |  Высота:  |  Размер: 169 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 507 KiB

После

Ширина:  |  Высота:  |  Размер: 507 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 168 KiB

После

Ширина:  |  Высота:  |  Размер: 166 KiB

Просмотреть файл

@@ -81,6 +81,7 @@ const AlertBanner = ({
return (
<div
id={id}
data-testid={id}
className={classNames(
'AlertBanner',

Просмотреть файл

@@ -72,6 +72,7 @@ exports[`components/channel_invite_modal/team_warning_banner should match snapsh
<div
className="AlertBanner warning AlertBanner--app"
data-testid="teamWarningBanner"
id="teamWarningBanner"
>
<div
className="AlertBanner__icon"
@@ -248,6 +249,7 @@ exports[`components/channel_invite_modal/team_warning_banner should match snapsh
<div
className="AlertBanner warning AlertBanner--app"
data-testid="teamWarningBanner"
id="teamWarningBanner"
>
<div
className="AlertBanner__icon"
@@ -498,6 +500,7 @@ exports[`components/channel_invite_modal/team_warning_banner should match snapsh
<div
className="AlertBanner warning AlertBanner--app"
data-testid="teamWarningBanner"
id="teamWarningBanner"
>
<div
className="AlertBanner__icon"
@@ -746,6 +749,7 @@ exports[`components/channel_invite_modal/team_warning_banner should match snapsh
<div
className="AlertBanner warning AlertBanner--app"
data-testid="teamWarningBanner"
id="teamWarningBanner"
>
<div
className="AlertBanner__icon"

Просмотреть файл

@@ -1314,6 +1314,7 @@ Object {
<div
class="AlertBanner info AlertBanner--app"
data-testid="channelNotificationsMutedBanner"
id="channelNotificationsMutedBanner"
>
<div
class="AlertBanner__icon"

Просмотреть файл

@@ -35,6 +35,12 @@
text-decoration: none;
}
&:focus {
padding: 8px;
border: 2px solid var(--sidebar-text-active-border);
margin-left: -10px;
}
svg {
width: 170px;
fill: var(--center-channel-color);

Просмотреть файл

@@ -16,6 +16,7 @@ import Constants, {WindowSizes} from 'utils/constants';
import type {GlobalState} from 'types/store';
jest.unmock('react-intl');
jest.unmock('react-router-dom');
describe('components/login/Login', () => {
@@ -277,6 +278,35 @@ describe('components/login/Login', () => {
});
});
it('should focus username field when there is an error', async () => {
const state = mergeObjects(baseState, {
entities: {
general: {
config: {
EnableSignInWithEmail: 'true',
},
},
},
});
renderWithContext(
<Login/>,
state,
{
intlMessages: {
'login.noEmail': 'Please enter your email',
},
},
);
// Try to submit without entering username
screen.getByRole('button', {name: 'Log in'}).click();
// Verify username field is focused
const usernameInput = screen.getByLabelText('Email');
expect(usernameInput).toHaveFocus();
});
it('should handle openid text and color props', () => {
const state = mergeObjects(baseState, {
entities: {

Просмотреть файл

@@ -460,6 +460,12 @@ const Login = ({onCustomizeHeader}: LoginProps) => {
};
}, []);
useEffect(() => {
if (hasError) {
loginIdInput.current?.focus();
}
}, [hasError]);
if (initializing) {
return (<LoadingScreen/>);
}
@@ -834,7 +840,6 @@ const Login = ({onCustomizeHeader}: LoginProps) => {
<div className={classNames('login-body-card', {'custom-branding': enableCustomBrand, 'with-error': hasError})}>
<div
className='login-body-card-content'
tabIndex={0}
>
<p className='login-body-card-title'>
{getCardTitle()}
@@ -842,6 +847,7 @@ const Login = ({onCustomizeHeader}: LoginProps) => {
{enableCustomBrand && getMessageSubtitle()}
{alertBanner && (
<AlertBanner
id='login-body-card-banner'
className='login-body-card-banner'
mode={alertBanner.mode}
title={alertBanner.title}
@@ -856,6 +862,7 @@ const Login = ({onCustomizeHeader}: LoginProps) => {
>
<div className='login-body-card-form'>
<Input
data-testid='login-id-input'
ref={loginIdInput}
name='loginId'
containerClassName='login-body-card-form-input'
@@ -867,6 +874,7 @@ const Login = ({onCustomizeHeader}: LoginProps) => {
placeholder={getInputPlaceholder()}
disabled={isWaiting}
autoFocus={true}
aria-describedby={alertBanner ? 'login-body-card-banner' : undefined}
/>
<PasswordInput
ref={passwordInput}

Просмотреть файл

@@ -71,4 +71,41 @@ describe('components/mfa/setup', () => {
wrapper.find('form').simulate('submit', {preventDefault: () => {}});
expect(baseProps.actions.activateMfa).toBeCalledWith('testcodeinput');
});
test('should focus input when code is empty', async () => {
const wrapper = mountWithIntl(
<Setup {...baseProps}/>,
);
const input = wrapper.find('input').getDOMNode() as HTMLInputElement;
const focusSpy = jest.spyOn(input, 'focus');
wrapper.find('form').simulate('submit', {preventDefault: () => {}});
expect(focusSpy).toHaveBeenCalled();
});
test('should focus input when authentication fails', async () => {
const props = {
...baseProps,
actions: {
...baseProps.actions,
activateMfa: jest.fn().mockImplementation(() => Promise.resolve({
error: {
server_error_id: 'ent.mfa.activate.authenticate.app_error',
message: 'Invalid code',
},
})),
},
};
const wrapper = mountWithIntl(
<Setup {...props}/>,
);
const input = wrapper.find('input').getDOMNode() as HTMLInputElement;
const focusSpy = jest.spyOn(input, 'focus');
(wrapper.instance() as Setup).input.current!.value = 'invalidcode';
wrapper.find('form').simulate('submit', {preventDefault: () => {}});
await new Promise((resolve) => setTimeout(resolve, 0)); // Wait for state update
expect(focusSpy).toHaveBeenCalled();
});
});

Просмотреть файл

@@ -105,6 +105,7 @@ export default class Setup extends React.PureComponent<Props, State> {
/>
),
});
this.input?.current?.focus();
return;
}
@@ -126,6 +127,7 @@ export default class Setup extends React.PureComponent<Props, State> {
error: error.message,
});
}
this.input?.current?.focus();
return;
}
@@ -137,7 +139,15 @@ export default class Setup extends React.PureComponent<Props, State> {
let formClass = 'form-group';
let errorContent;
if (this.state.error) {
errorContent = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
errorContent = (
<div
id='mfa-setup-error'
className='form-group has-error'
role='alert'
>
<label className='control-label'>{this.state.error}</label>
</div>
);
formClass += ' has-error';
}
@@ -212,6 +222,7 @@ export default class Setup extends React.PureComponent<Props, State> {
className='form-control'
placeholder={defineMessage({id: 'mfa.setup.code', defaultMessage: 'MFA Code'})}
autoFocus={true}
aria-describedby={this.state.error ? 'mfa-setup-error' : undefined}
/>
</p>
{errorContent}

Просмотреть файл

@@ -35,8 +35,6 @@ exports[`components/signup/Signup should match snapshot for all signup options e
>
<div
className="signup-body-card-content"
onKeyDown={[Function]}
tabIndex={0}
>
<p
className="signup-body-card-title"
@@ -107,21 +105,20 @@ exports[`components/signup/Signup should match snapshot for all signup options e
>
<div
className="signup-body-card-content"
onKeyDown={[Function]}
tabIndex={0}
>
<p
className="signup-body-card-title"
>
Create your account
</p>
<div
<form
className="signup-body-card-form"
>
<ForwardRef
autoFocus={true}
className="signup-body-card-form-email-input"
customMessage={null}
data-testid="signup-body-card-form-email-input"
disabled={false}
inputSize="large"
name="email"
@@ -140,6 +137,7 @@ exports[`components/signup/Signup should match snapshot for all signup options e
"value": "You can use lowercase letters, numbers, periods, dashes, and underscores.",
}
}
data-testid="signup-body-card-form-name-input"
disabled={false}
inputSize="large"
name="name"
@@ -152,6 +150,7 @@ exports[`components/signup/Signup should match snapshot for all signup options e
<ForwardRef
className="signup-body-card-form-password-input"
createMode={true}
data-testid="signup-body-card-form-password-input"
disabled={false}
error=""
info="Must be 5-72 characters long."
@@ -190,7 +189,7 @@ exports[`components/signup/Signup should match snapshot for all signup options e
saving={false}
savingMessage="Creating account…"
/>
</div>
</form>
<div
className="signup-body-card-form-divider"
>
@@ -275,21 +274,20 @@ exports[`components/signup/Signup should match snapshot for all signup options e
>
<div
className="signup-body-card-content"
onKeyDown={[Function]}
tabIndex={0}
>
<p
className="signup-body-card-title"
>
Create your account
</p>
<div
<form
className="signup-body-card-form"
>
<ForwardRef
autoFocus={true}
className="signup-body-card-form-email-input"
customMessage={null}
data-testid="signup-body-card-form-email-input"
disabled={false}
inputSize="large"
name="email"
@@ -308,6 +306,7 @@ exports[`components/signup/Signup should match snapshot for all signup options e
"value": "You can use lowercase letters, numbers, periods, dashes, and underscores.",
}
}
data-testid="signup-body-card-form-name-input"
disabled={false}
inputSize="large"
name="name"
@@ -320,6 +319,7 @@ exports[`components/signup/Signup should match snapshot for all signup options e
<ForwardRef
className="signup-body-card-form-password-input"
createMode={true}
data-testid="signup-body-card-form-password-input"
disabled={false}
error=""
info="Must be 5-72 characters long."
@@ -358,7 +358,7 @@ exports[`components/signup/Signup should match snapshot for all signup options e
saving={false}
savingMessage="Creating account…"
/>
</div>
</form>
<div
className="signup-body-card-form-divider"
>

Просмотреть файл

@@ -18,7 +18,7 @@ import Input from 'components/widgets/inputs/input/input';
import PasswordInput from 'components/widgets/inputs/password_input/password_input';
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
import {act, renderWithContext, screen} from 'tests/react_testing_utils';
import {act, renderWithContext, screen, fireEvent, waitFor} from 'tests/react_testing_utils';
import {WindowSizes} from 'utils/constants';
import type {GlobalState} from 'types/store';
@@ -274,6 +274,67 @@ describe('components/signup/Signup', () => {
expect(wrapper.find(PasswordInput).first().props().disabled).toEqual(true);
});
it('should focus email input when email validation fails', async () => {
renderWithContext(<Signup/>, mockState);
const emailInput = screen.getByTestId('signup-body-card-form-email-input');
const submitButton = screen.getByText('Create account');
// Submit with invalid email
fireEvent.change(emailInput, {target: {value: 'invalid-email'}});
fireEvent.click(submitButton);
await waitFor(() => {
expect(emailInput).toHaveFocus();
});
});
it('should focus password input when password validation fails', async () => {
renderWithContext(<Signup/>, mockState);
const emailInput = screen.getByTestId('signup-body-card-form-email-input');
const usernameInput = screen.getByTestId('signup-body-card-form-name-input');
const passwordInput = screen.getByTestId('signup-body-card-form-password-input');
const submitButton = screen.getByText('Create account');
// Submit with valid email and username but invalid password
fireEvent.change(emailInput, {target: {value: 'test@example.com'}});
fireEvent.change(usernameInput, {target: {value: 'testuser'}});
fireEvent.change(passwordInput, {target: {value: '123'}});
fireEvent.click(submitButton);
await waitFor(() => {
expect(passwordInput).toHaveFocus();
});
});
it('should focus username input when server returns username exists error', async () => {
mockDispatch = jest.fn().mockImplementation(() => Promise.resolve({
data: {},
error: {
server_error_id: 'app.user.save.username_exists.app_error',
message: 'Username already exists',
},
}));
renderWithContext(<Signup/>, mockState);
const emailInput = screen.getByTestId('signup-body-card-form-email-input');
const usernameInput = screen.getByTestId('signup-body-card-form-name-input');
const passwordInput = screen.getByTestId('signup-body-card-form-password-input');
const submitButton = screen.getByText('Create account');
// Submit with valid data that will trigger server error
fireEvent.change(emailInput, {target: {value: 'test@example.com'}});
fireEvent.change(usernameInput, {target: {value: 'existinguser'}});
fireEvent.change(passwordInput, {target: {value: 'password123'}});
fireEvent.click(submitButton);
await waitFor(() => {
expect(usernameInput).toHaveFocus();
});
});
it('should add user to team and redirect when team invite valid and logged in', async () => {
mockLocation.search = '?id=ppni7a9t87fn3j4d56rwocdctc';

Просмотреть файл

@@ -142,6 +142,7 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
const [alertBanner, setAlertBanner] = useState<AlertBannerProps | null>(null);
const [isMobileView, setIsMobileView] = useState(false);
const [subscribeToSecurityNewsletter, setSubscribeToSecurityNewsletter] = useState(false);
const [submitClicked, setSubmitClicked] = useState(false);
const cwsAvailability = useCWSAvailabilityCheck();
@@ -368,6 +369,19 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
}
}, [onCustomizeHeader, handleHeaderBackButtonOnClick, isMobileView, getAlternateLink, search]);
useEffect(() => {
if (submitClicked) {
if (emailError && emailInput.current) {
emailInput.current.focus();
} else if (nameError && nameInput.current) {
nameInput.current.focus();
} else if (passwordError && passwordInput.current) {
passwordInput.current.focus();
}
setSubmitClicked(false);
}
}, [emailError, nameError, passwordError, submitClicked]);
if (loading) {
return (<LoadingScreen/>);
}
@@ -376,12 +390,6 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
setBrandImageError(true);
};
const onEnterKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === Constants.KeyCodes.ENTER[0] && canSubmit) {
handleSubmit(e);
}
};
const getCardTitle = () => {
if (CustomDescriptionText) {
return CustomDescriptionText;
@@ -579,6 +587,7 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
e.preventDefault();
sendSignUpTelemetryEvents('click_create_account', getRoleFromTrackFlow());
setIsWaiting(true);
setSubmitClicked(true);
if (isUserValid()) {
setNameError('');
@@ -604,6 +613,12 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
onDismiss: dismissAlert,
});
setIsWaiting(false);
// Special case for accessibility to show the error message when the username is already taken
if (error.server_error_id === 'app.user.save.username_exists.app_error') {
setNameError(error.message);
setSubmitClicked(true);
}
return;
}
@@ -785,8 +800,6 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
<div className={classNames('signup-body-card', {'custom-branding': enableCustomBrand, 'with-error': hasError})}>
<div
className='signup-body-card-content'
onKeyDown={onEnterKeyDown}
tabIndex={0}
>
<p className='signup-body-card-title'>
{getCardTitle()}
@@ -801,8 +814,9 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
/>
)}
{enableSignUpWithEmail && (
<div className='signup-body-card-form'>
<form className='signup-body-card-form'>
<Input
data-testid='signup-body-card-form-email-input'
ref={emailInput}
name='email'
className='signup-body-card-form-email-input'
@@ -820,6 +834,7 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
onBlur={(e) => handleOnBlur(e, 'email')}
/>
<Input
data-testid='signup-body-card-form-name-input'
ref={nameInput}
name='name'
className='signup-body-card-form-name-input'
@@ -842,6 +857,7 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
onBlur={(e) => handleOnBlur(e, 'username')}
/>
<PasswordInput
data-testid='signup-body-card-form-password-input'
ref={passwordInput}
className='signup-body-card-form-password-input'
value={password}
@@ -862,7 +878,7 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
defaultMessage={formatMessage({id: 'signup_user_completed.create', defaultMessage: 'Create account'})}
savingMessage={formatMessage({id: 'signup_user_completed.saving', defaultMessage: 'Creating account…'})}
/>
</div>
</form>
)}
{enableSignUpWithEmail && enableExternalSignup && (
<div className='signup-body-card-form-divider'>

Просмотреть файл

@@ -40,6 +40,7 @@ const PasswordInput = React.forwardRef((
createMode,
disabled,
inputSize,
...otherProps
}: PasswordInputProps,
ref?: React.Ref<HTMLInputElement>,
) => {
@@ -66,7 +67,10 @@ const PasswordInput = React.forwardRef((
<button
id='password_toggle'
type='button'
aria-label={formatMessage({id: 'widget.passwordInput.passwordToggle', defaultMessage: 'Show or hide password'})}
aria-label={formatMessage({
id: showPassword ? 'widget.passwordInput.hidePassword' : 'widget.passwordInput.showPassword',
defaultMessage: showPassword ? 'Hide password' : 'Show password',
})}
className='password-input-toggle'
onClick={toggleShowPassword}
disabled={disabled}
@@ -86,6 +90,7 @@ const PasswordInput = React.forwardRef((
customMessage={error || info ? customMessage : undefined}
disabled={disabled}
ref={ref}
{...otherProps}
/>
);
});

Просмотреть файл

@@ -5974,22 +5974,22 @@
"user.settings.security.oneSignin": "You may only have one sign-in method at a time. Switching sign-in method will send an email notifying you if the change was successful.",
"user.settings.security.openid": "OpenID",
"user.settings.security.password": "Password",
"user.settings.security.passwordError": "Must be {min}-{max} characters long.",
"user.settings.security.passwordErrorLowercase": "Must be {min}-{max} characters long and include lowercase letters.",
"user.settings.security.passwordErrorLowercaseNumber": "Must be {min}-{max} characters long and include lowercase letters and numbers.",
"user.settings.security.passwordErrorLowercaseNumberSymbol": "Must be {min}-{max} characters long and include lowercase letters, numbers, and special characters.",
"user.settings.security.passwordErrorLowercaseSymbol": "Must be {min}-{max} characters long and include lowercase letters and special characters.",
"user.settings.security.passwordErrorLowercaseUppercase": "Must be {min}-{max} characters long and include both lowercase and uppercase letters.",
"user.settings.security.passwordErrorLowercaseUppercaseNumber": "Must be {min}-{max} characters long and include both lowercase and uppercase letters, and numbers.",
"user.settings.security.passwordErrorLowercaseUppercaseNumberSymbol": "Must be {min}-{max} characters long and include both lowercase and uppercase letters, numbers, and special characters.",
"user.settings.security.passwordErrorLowercaseUppercaseSymbol": "Must be {min}-{max} characters long and include both lowercase and uppercase letters, and special characters.",
"user.settings.security.passwordErrorNumber": "Must be {min}-{max} characters long and include numbers.",
"user.settings.security.passwordErrorNumberSymbol": "Must be {min}-{max} characters long and include numbers and special characters.",
"user.settings.security.passwordErrorSymbol": "Must be {min}-{max} characters long and include special characters.",
"user.settings.security.passwordErrorUppercase": "Must be {min}-{max} characters long and include uppercase letters.",
"user.settings.security.passwordErrorUppercaseNumber": "Must be {min}-{max} characters long and include uppercase letters, and numbers.",
"user.settings.security.passwordErrorUppercaseNumberSymbol": "Must be {min}-{max} characters long and include uppercase letters, numbers, and special characters.",
"user.settings.security.passwordErrorUppercaseSymbol": "Must be {min}-{max} characters long and include uppercase letters, and special characters.",
"user.settings.security.passwordError": "Your password must be {min}-{max} characters long.",
"user.settings.security.passwordErrorLowercase": "Your password must be {min}-{max} characters long and include lowercase letters.",
"user.settings.security.passwordErrorLowercaseNumber": "Your password must be {min}-{max} characters long and include lowercase letters and numbers.",
"user.settings.security.passwordErrorLowercaseNumberSymbol": "Your password must be {min}-{max} characters long and include lowercase letters, numbers, and special characters.",
"user.settings.security.passwordErrorLowercaseSymbol": "Your password must be {min}-{max} characters long and include lowercase letters and special characters.",
"user.settings.security.passwordErrorLowercaseUppercase": "Your password must be {min}-{max} characters long and include both lowercase and uppercase letters.",
"user.settings.security.passwordErrorLowercaseUppercaseNumber": "Your password must be {min}-{max} characters long and include both lowercase and uppercase letters, and numbers.",
"user.settings.security.passwordErrorLowercaseUppercaseNumberSymbol": "Your password must be {min}-{max} characters long and include both lowercase and uppercase letters, numbers, and special characters.",
"user.settings.security.passwordErrorLowercaseUppercaseSymbol": "Your password must be {min}-{max} characters long and include both lowercase and uppercase letters, and special characters.",
"user.settings.security.passwordErrorNumber": "Your password must be {min}-{max} characters long and include numbers.",
"user.settings.security.passwordErrorNumberSymbol": "Your password must be {min}-{max} characters long and include numbers and special characters.",
"user.settings.security.passwordErrorSymbol": "Your password must be {min}-{max} characters long and include special characters.",
"user.settings.security.passwordErrorUppercase": "Your password must be {min}-{max} characters long and include uppercase letters.",
"user.settings.security.passwordErrorUppercaseNumber": "Your password must be {min}-{max} characters long and include uppercase letters, and numbers.",
"user.settings.security.passwordErrorUppercaseNumberSymbol": "Your password must be {min}-{max} characters long and include uppercase letters, numbers, and special characters.",
"user.settings.security.passwordErrorUppercaseSymbol": "Your password must be {min}-{max} characters long and include uppercase letters, and special characters.",
"user.settings.security.passwordGitlabCantUpdate": "Login occurs through GitLab. Password cannot be updated.",
"user.settings.security.passwordGoogleCantUpdate": "Login occurs through Google Apps. Password cannot be updated.",
"user.settings.security.passwordLdapCantUpdate": "Login occurs through AD/LDAP. Password cannot be updated.",
@@ -6130,7 +6130,6 @@
"widget.input.required": "This field is required",
"widget.passwordInput.createPassword": "Choose a Password",
"widget.passwordInput.password": "Password",
"widget.passwordInput.passwordToggle": "Show or hide password",
"widgets.channels_input.empty": "No channels found",
"widgets.channels_input.loading": "Loading",
"widgets.users_emails_input.loading": "Loading",

Просмотреть файл

@@ -85,20 +85,20 @@ export function isValidPassword(password: string, passwordConfig: PasswordConfig
}
export const passwordErrors = defineMessages({
passwordError: {id: 'user.settings.security.passwordError', defaultMessage: 'Must be {min}-{max} characters long.'},
passwordErrorLowercase: {id: 'user.settings.security.passwordErrorLowercase', defaultMessage: 'Must be {min}-{max} characters long and include lowercase letters.'},
passwordErrorLowercaseNumber: {id: 'user.settings.security.passwordErrorLowercaseNumber', defaultMessage: 'Must be {min}-{max} characters long and include lowercase letters and numbers.'},
passwordErrorLowercaseNumberSymbol: {id: 'user.settings.security.passwordErrorLowercaseNumberSymbol', defaultMessage: 'Must be {min}-{max} characters long and include lowercase letters, numbers, and special characters.'},
passwordErrorLowercaseSymbol: {id: 'user.settings.security.passwordErrorLowercaseSymbol', defaultMessage: 'Must be {min}-{max} characters long and include lowercase letters and special characters.'},
passwordErrorLowercaseUppercase: {id: 'user.settings.security.passwordErrorLowercaseUppercase', defaultMessage: 'Must be {min}-{max} characters long and include both lowercase and uppercase letters.'},
passwordErrorLowercaseUppercaseNumber: {id: 'user.settings.security.passwordErrorLowercaseUppercaseNumber', defaultMessage: 'Must be {min}-{max} characters long and include both lowercase and uppercase letters, and numbers.'},
passwordErrorLowercaseUppercaseNumberSymbol: {id: 'user.settings.security.passwordErrorLowercaseUppercaseNumberSymbol', defaultMessage: 'Must be {min}-{max} characters long and include both lowercase and uppercase letters, numbers, and special characters.'},
passwordErrorLowercaseUppercaseSymbol: {id: 'user.settings.security.passwordErrorLowercaseUppercaseSymbol', defaultMessage: 'Must be {min}-{max} characters long and include both lowercase and uppercase letters, and special characters.'},
passwordErrorNumber: {id: 'user.settings.security.passwordErrorNumber', defaultMessage: 'Must be {min}-{max} characters long and include numbers.'},
passwordErrorNumberSymbol: {id: 'user.settings.security.passwordErrorNumberSymbol', defaultMessage: 'Must be {min}-{max} characters long and include numbers and special characters.'},
passwordErrorSymbol: {id: 'user.settings.security.passwordErrorSymbol', defaultMessage: 'Must be {min}-{max} characters long and include special characters.'},
passwordErrorUppercase: {id: 'user.settings.security.passwordErrorUppercase', defaultMessage: 'Must be {min}-{max} characters long and include uppercase letters.'},
passwordErrorUppercaseNumber: {id: 'user.settings.security.passwordErrorUppercaseNumber', defaultMessage: 'Must be {min}-{max} characters long and include uppercase letters, and numbers.'},
passwordErrorUppercaseNumberSymbol: {id: 'user.settings.security.passwordErrorUppercaseNumberSymbol', defaultMessage: 'Must be {min}-{max} characters long and include uppercase letters, numbers, and special characters.'},
passwordErrorUppercaseSymbol: {id: 'user.settings.security.passwordErrorUppercaseSymbol', defaultMessage: 'Must be {min}-{max} characters long and include uppercase letters, and special characters.'},
passwordError: {id: 'user.settings.security.passwordError', defaultMessage: 'Your password must be {min}-{max} characters long.'},
passwordErrorLowercase: {id: 'user.settings.security.passwordErrorLowercase', defaultMessage: 'Your password must be {min}-{max} characters long and include lowercase letters.'},
passwordErrorLowercaseNumber: {id: 'user.settings.security.passwordErrorLowercaseNumber', defaultMessage: 'Your password must be {min}-{max} characters long and include lowercase letters and numbers.'},
passwordErrorLowercaseNumberSymbol: {id: 'user.settings.security.passwordErrorLowercaseNumberSymbol', defaultMessage: 'Your password must be {min}-{max} characters long and include lowercase letters, numbers, and special characters.'},
passwordErrorLowercaseSymbol: {id: 'user.settings.security.passwordErrorLowercaseSymbol', defaultMessage: 'Your password must be {min}-{max} characters long and include lowercase letters and special characters.'},
passwordErrorLowercaseUppercase: {id: 'user.settings.security.passwordErrorLowercaseUppercase', defaultMessage: 'Your password must be {min}-{max} characters long and include both lowercase and uppercase letters.'},
passwordErrorLowercaseUppercaseNumber: {id: 'user.settings.security.passwordErrorLowercaseUppercaseNumber', defaultMessage: 'Your password must be {min}-{max} characters long and include both lowercase and uppercase letters, and numbers.'},
passwordErrorLowercaseUppercaseNumberSymbol: {id: 'user.settings.security.passwordErrorLowercaseUppercaseNumberSymbol', defaultMessage: 'Your password must be {min}-{max} characters long and include both lowercase and uppercase letters, numbers, and special characters.'},
passwordErrorLowercaseUppercaseSymbol: {id: 'user.settings.security.passwordErrorLowercaseUppercaseSymbol', defaultMessage: 'Your password must be {min}-{max} characters long and include both lowercase and uppercase letters, and special characters.'},
passwordErrorNumber: {id: 'user.settings.security.passwordErrorNumber', defaultMessage: 'Your password must be {min}-{max} characters long and include numbers.'},
passwordErrorNumberSymbol: {id: 'user.settings.security.passwordErrorNumberSymbol', defaultMessage: 'Your password must be {min}-{max} characters long and include numbers and special characters.'},
passwordErrorSymbol: {id: 'user.settings.security.passwordErrorSymbol', defaultMessage: 'Your password must be {min}-{max} characters long and include special characters.'},
passwordErrorUppercase: {id: 'user.settings.security.passwordErrorUppercase', defaultMessage: 'Your password must be {min}-{max} characters long and include uppercase letters.'},
passwordErrorUppercaseNumber: {id: 'user.settings.security.passwordErrorUppercaseNumber', defaultMessage: 'Your password must be {min}-{max} characters long and include uppercase letters, and numbers.'},
passwordErrorUppercaseNumberSymbol: {id: 'user.settings.security.passwordErrorUppercaseNumberSymbol', defaultMessage: 'Your password must be {min}-{max} characters long and include uppercase letters, numbers, and special characters.'},
passwordErrorUppercaseSymbol: {id: 'user.settings.security.passwordErrorUppercaseSymbol', defaultMessage: 'Your password must be {min}-{max} characters long and include uppercase letters, and special characters.'},
});