[MM-63041] Convert many inputs to the Input component, replace clientError with more correct client-side validation that conforms to the input (#31279)
* [MM-63041] Convert many inputs to the Input component, replace clientError with more correct client-side validation that conforms to the input * Fix line length * PR feedback --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
e6be282568
Коммит
65d3d5984f
@@ -36,7 +36,7 @@ describe('Account Settings', () => {
|
|||||||
cy.uiClose();
|
cy.uiClose();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('MM-T2081 Password: Error on blank', () => {
|
it('MM-T2081 Password: Save should be disabled on blank', () => {
|
||||||
// # Go to Profile > Security
|
// # Go to Profile > Security
|
||||||
cy.uiOpenProfileModal('Security');
|
cy.uiOpenProfileModal('Security');
|
||||||
|
|
||||||
@@ -49,12 +49,8 @@ describe('Account Settings', () => {
|
|||||||
// # Click "Edit" to the right of "Password"
|
// # Click "Edit" to the right of "Password"
|
||||||
cy.get('#passwordEdit').should('be.visible').click();
|
cy.get('#passwordEdit').should('be.visible').click();
|
||||||
|
|
||||||
// # Save the settings
|
// # Check that save button is disabled
|
||||||
cy.uiSave();
|
cy.get('button[type="submit"]').should('be.disabled');
|
||||||
|
|
||||||
// * Check that there is an error
|
|
||||||
cy.get('#clientError').should('be.visible').should('contain', 'Please enter your current password.');
|
|
||||||
cy.get('#serverError').should('not.exist');
|
|
||||||
|
|
||||||
cy.uiClose();
|
cy.uiClose();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -81,11 +81,11 @@ describe('Profile > Profile Settings > Email', () => {
|
|||||||
// # Click "Edit" to the right of "Email"
|
// # Click "Edit" to the right of "Email"
|
||||||
cy.get('#emailEdit').should('be.visible').click();
|
cy.get('#emailEdit').should('be.visible').click();
|
||||||
|
|
||||||
// # Save the settings
|
// # Click on the input and blur it
|
||||||
cy.uiSave().wait(TIMEOUTS.HALF_SEC);
|
cy.get('#primaryEmail').should('be.visible').click().blur();
|
||||||
|
|
||||||
// * Check that the correct error message is shown.
|
// * Check that the correct error message is shown.
|
||||||
cy.get('#clientError').should('be.visible').should('have.text', 'Please enter a valid email address');
|
cy.get('#error_primaryEmail').should('be.visible').should('have.text', 'Please enter a valid email address');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('MM-T2067 email address already taken error', () => {
|
it('MM-T2067 email address already taken error', () => {
|
||||||
@@ -113,11 +113,8 @@ describe('Profile > Profile Settings > Email', () => {
|
|||||||
cy.get('#confirmEmail').should('be.visible').clear();
|
cy.get('#confirmEmail').should('be.visible').clear();
|
||||||
cy.get('#currentPassword').should('be.visible').type('randompass');
|
cy.get('#currentPassword').should('be.visible').type('randompass');
|
||||||
|
|
||||||
// # Save the settings
|
|
||||||
cy.uiSave().wait(TIMEOUTS.HALF_SEC);
|
|
||||||
|
|
||||||
// * Check that the correct error message is shown.
|
// * Check that the correct error message is shown.
|
||||||
cy.get('#clientError').should('be.visible').should('have.text', 'The new emails you entered do not match.');
|
cy.get('#error_confirmEmail').should('be.visible').should('have.text', 'The new emails you entered do not match.');
|
||||||
});
|
});
|
||||||
|
|
||||||
// This test is a combination of 4 sub-tests because they are sub-parts of the same test.
|
// This test is a combination of 4 sub-tests because they are sub-parts of the same test.
|
||||||
|
|||||||
@@ -47,11 +47,10 @@ describe('Settings > Sidebar > General > Edit', () => {
|
|||||||
it('MM-T2050 Username cannot be blank', () => {
|
it('MM-T2050 Username cannot be blank', () => {
|
||||||
// # Clear the username textfield contents
|
// # Clear the username textfield contents
|
||||||
cy.get('#usernameEdit').click();
|
cy.get('#usernameEdit').click();
|
||||||
cy.get('#username').clear();
|
cy.get('#username').click().clear().blur();
|
||||||
cy.uiSave();
|
|
||||||
|
|
||||||
// * Check if element is present and contains expected text values
|
// * Check if element is present and contains expected text values
|
||||||
cy.get('#clientError').should('be.visible').should('contain', 'Username must begin with a letter, and contain between 3 to 22 lowercase characters made up of numbers, letters, and the symbols \'.\', \'-\', and \'_\'.');
|
cy.get('#error_username').should('be.visible').should('contain', 'Username must begin with a letter, and contain between 3 to 22 lowercase characters made up of numbers, letters, and the symbols \'.\', \'-\', and \'_\'.');
|
||||||
|
|
||||||
// # Click "x" button to close Profile modal
|
// # Click "x" button to close Profile modal
|
||||||
cy.uiClose();
|
cy.uiClose();
|
||||||
@@ -62,11 +61,10 @@ describe('Settings > Sidebar > General > Edit', () => {
|
|||||||
cy.get('#usernameEdit').click();
|
cy.get('#usernameEdit').click();
|
||||||
|
|
||||||
// # Add the username to textfield contents
|
// # Add the username to textfield contents
|
||||||
cy.get('#username').clear().type('te');
|
cy.get('#username').clear().type('te').blur();
|
||||||
cy.uiSave();
|
|
||||||
|
|
||||||
// * Check if element is present and contains expected text values
|
// * Check if element is present and contains expected text values
|
||||||
cy.get('#clientError').should('be.visible').should('contain', 'Username must begin with a letter, and contain between 3 to 22 lowercase characters made up of numbers, letters, and the symbols \'.\', \'-\', and \'_\'.');
|
cy.get('#error_username').should('be.visible').should('contain', 'Username must begin with a letter, and contain between 3 to 22 lowercase characters made up of numbers, letters, and the symbols \'.\', \'-\', and \'_\'.');
|
||||||
|
|
||||||
// # Click "x" button to close Profile modal
|
// # Click "x" button to close Profile modal
|
||||||
cy.uiClose();
|
cy.uiClose();
|
||||||
@@ -151,11 +149,10 @@ describe('Settings > Sidebar > General > Edit', () => {
|
|||||||
|
|
||||||
for (const prefix of prefixes) {
|
for (const prefix of prefixes) {
|
||||||
// # Add username to textfield contents
|
// # Add username to textfield contents
|
||||||
cy.get('#username').clear().type(prefix).type('{backspace}.').type(otherUser.username);
|
cy.get('#username').clear().type(prefix).type('{backspace}.').type(otherUser.username).blur();
|
||||||
cy.uiSave();
|
|
||||||
|
|
||||||
// * Check if element is present and contains expected text values
|
// * Check if element is present and contains expected text values
|
||||||
cy.get('#clientError').should('be.visible').should('contain', 'Username must begin with a letter, and contain between 3 to 22 lowercase characters made up of numbers, letters, and the symbols \'.\', \'-\', and \'_\'.');
|
cy.get('#error_username').should('be.visible').should('contain', 'Username must begin with a letter, and contain between 3 to 22 lowercase characters made up of numbers, letters, and the symbols \'.\', \'-\', and \'_\'.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// # Click "x" button to close Profile modal
|
// # Click "x" button to close Profile modal
|
||||||
@@ -175,11 +172,10 @@ describe('Settings > Sidebar > General > Edit', () => {
|
|||||||
|
|
||||||
for (const username of usernames) {
|
for (const username of usernames) {
|
||||||
// # Add username to textfield contents
|
// # Add username to textfield contents
|
||||||
cy.get('#username').clear().type(username);
|
cy.get('#username').clear().type(username).blur();
|
||||||
cy.uiSave();
|
|
||||||
|
|
||||||
// * Check if element is present and contains expected text values
|
// * Check if element is present and contains expected text values
|
||||||
cy.get('#clientError').should('be.visible').should('contain', 'This username is reserved, please choose a new one.');
|
cy.get('#error_username').should('be.visible').should('contain', 'This username is reserved, please choose a new one.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// # Click "x" button to close Profile modal
|
// # Click "x" button to close Profile modal
|
||||||
|
|||||||
@@ -54,11 +54,15 @@ describe('Profile', () => {
|
|||||||
// # Enter valid values in password change fields
|
// # Enter valid values in password change fields
|
||||||
enterPasswords(testUser.password, 'passwd', 'passwd');
|
enterPasswords(testUser.password, 'passwd', 'passwd');
|
||||||
|
|
||||||
|
// * Check that there are no errors
|
||||||
|
cy.get('#error_currentPassword').should('not.exist');
|
||||||
|
cy.get('#error_newPassword').should('not.exist');
|
||||||
|
cy.get('#error_confirmPassword').should('not.exist');
|
||||||
|
|
||||||
// # Save the settings
|
// # Save the settings
|
||||||
cy.uiSave();
|
cy.uiSave();
|
||||||
|
|
||||||
// * Check that there are no errors
|
// * Check that there are no errors
|
||||||
cy.get('#clientError').should('not.exist');
|
|
||||||
cy.get('#serverError').should('not.exist');
|
cy.get('#serverError').should('not.exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -66,22 +70,16 @@ describe('Profile', () => {
|
|||||||
// # Enter mismatching passwords for new password and confirm fields
|
// # Enter mismatching passwords for new password and confirm fields
|
||||||
enterPasswords(testUser.password, 'newPW', 'NewPW');
|
enterPasswords(testUser.password, 'newPW', 'NewPW');
|
||||||
|
|
||||||
// # Save
|
|
||||||
cy.uiSave();
|
|
||||||
|
|
||||||
// * Verify for error message: "The new passwords you entered do not match."
|
// * Verify for error message: "The new passwords you entered do not match."
|
||||||
cy.get('#clientError').should('be.visible').should('have.text', 'The new passwords you entered do not match.');
|
cy.get('#error_confirmPassword').should('be.visible').should('have.text', 'The new passwords you entered do not match.');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('MM-T2083 Password: Too few characters in new password produces error', () => {
|
it('MM-T2083 Password: Too few characters in new password produces error', () => {
|
||||||
// # Enter a New password two letters long
|
// # Enter a New password two letters long
|
||||||
enterPasswords(testUser.password, 'pw', 'pw');
|
enterPasswords(testUser.password, 'pw', 'pw');
|
||||||
|
|
||||||
// # Save
|
|
||||||
cy.uiSave();
|
|
||||||
|
|
||||||
// * Verify for error message: "Your password 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.');
|
cy.get('#error_newPassword').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', () => {
|
it('MM-T2084 Password: Cancel out of password changes causes no changes to be made', () => {
|
||||||
@@ -151,4 +149,7 @@ function enterPasswords(currentPassword, newPassword, confirmPassword) {
|
|||||||
|
|
||||||
// # Retype New password incorrectly
|
// # Retype New password incorrectly
|
||||||
cy.get('#confirmPassword').should('be.visible').type(confirmPassword);
|
cy.get('#confirmPassword').should('be.visible').type(confirmPassword);
|
||||||
|
|
||||||
|
// # Click on the input and blur it
|
||||||
|
cy.get('#currentPassword').should('be.visible').click().blur();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ export default class ProfileModal {
|
|||||||
readonly saveButton;
|
readonly saveButton;
|
||||||
readonly cancelButton;
|
readonly cancelButton;
|
||||||
|
|
||||||
readonly errorText;
|
|
||||||
|
|
||||||
constructor(container: Locator) {
|
constructor(container: Locator) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
|
|
||||||
@@ -30,8 +28,6 @@ export default class ProfileModal {
|
|||||||
this.closeButton = container.getByRole('button', {name: 'Close'});
|
this.closeButton = container.getByRole('button', {name: 'Close'});
|
||||||
this.saveButton = container.locator('button:has-text("Save")');
|
this.saveButton = container.locator('button:has-text("Save")');
|
||||||
this.cancelButton = container.locator('button:has-text("Cancel")');
|
this.cancelButton = container.locator('button:has-text("Cancel")');
|
||||||
|
|
||||||
this.errorText = container.locator('#clientError');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async toBeVisible() {
|
async toBeVisible() {
|
||||||
|
|||||||
@@ -346,13 +346,13 @@ test('MM-T5772 URL Validation in Custom Profile Attributes @custom_profile_attri
|
|||||||
await profileModal.container.locator(`#customAttribute_${fieldId}`).scrollIntoViewIfNeeded();
|
await profileModal.container.locator(`#customAttribute_${fieldId}`).scrollIntoViewIfNeeded();
|
||||||
await profileModal.container.locator(`#customAttribute_${fieldId}`).clear();
|
await profileModal.container.locator(`#customAttribute_${fieldId}`).clear();
|
||||||
await profileModal.container.locator(`#customAttribute_${fieldId}`).fill(TEST_INVALID_URL);
|
await profileModal.container.locator(`#customAttribute_${fieldId}`).fill(TEST_INVALID_URL);
|
||||||
|
await profileModal.container.locator(`#customAttribute_${fieldId}`).blur();
|
||||||
// 4. Try to save the changes
|
|
||||||
await profileModal.saveButton.click();
|
|
||||||
|
|
||||||
// * Save button doesn't complete the operation with invalid URL
|
// * Save button doesn't complete the operation with invalid URL
|
||||||
await expect(profileModal.errorText).toBeVisible();
|
await expect(profileModal.container.locator(`#error_customAttribute_${fieldId}`)).toBeVisible();
|
||||||
await expect(profileModal.errorText).toHaveText('Please enter a valid url.');
|
await expect(profileModal.container.locator(`#error_customAttribute_${fieldId}`)).toHaveText(
|
||||||
|
'Please enter a valid url.',
|
||||||
|
);
|
||||||
|
|
||||||
// 5. Edit Website field and enter a valid URL
|
// 5. Edit Website field and enter a valid URL
|
||||||
await profileModal.container.locator(`#customAttribute_${fieldId}`).clear();
|
await profileModal.container.locator(`#customAttribute_${fieldId}`).clear();
|
||||||
@@ -362,6 +362,6 @@ test('MM-T5772 URL Validation in Custom Profile Attributes @custom_profile_attri
|
|||||||
await profileModal.saveButton.click();
|
await profileModal.saveButton.click();
|
||||||
|
|
||||||
// * Valid URL saves successfully with no error message
|
// * Valid URL saves successfully with no error message
|
||||||
await expect(profileModal.errorText).not.toBeVisible();
|
await expect(profileModal.container.locator(`#error_customAttribute_${fieldId}`)).not.toBeVisible();
|
||||||
await expect(profileModal.container).toContainText(TEST_VALID_URL);
|
await expect(profileModal.container).toContainText(TEST_VALID_URL);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -78,33 +78,7 @@ exports[`components/SettingItemMax should match snapshot, on clientError 1`] = `
|
|||||||
<hr />
|
<hr />
|
||||||
<div
|
<div
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
/>
|
||||||
<div
|
|
||||||
className="form-group"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
className="col-sm-12 has-error"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="icon icon-alert-circle-outline"
|
|
||||||
role="img"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="sr-only"
|
|
||||||
>
|
|
||||||
<MemoizedFormattedMessage
|
|
||||||
defaultMessage="Error"
|
|
||||||
id="setting_item_max.error"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
id="clientError"
|
|
||||||
>
|
|
||||||
clientError
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<SaveButton
|
<SaveButton
|
||||||
disabled={false}
|
disabled={false}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
|
|||||||
@@ -19,11 +19,6 @@ type Props = {
|
|||||||
containerStyle?: string;
|
containerStyle?: string;
|
||||||
serverError?: ReactNode;
|
serverError?: ReactNode;
|
||||||
|
|
||||||
/**
|
|
||||||
* Client error
|
|
||||||
*/
|
|
||||||
clientError?: ReactNode;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings extra information
|
* Settings extra information
|
||||||
*/
|
*/
|
||||||
@@ -51,6 +46,7 @@ type Props = {
|
|||||||
shiftEnter?: boolean;
|
shiftEnter?: boolean;
|
||||||
saveButtonText?: string;
|
saveButtonText?: string;
|
||||||
saveButtonClassName?: string;
|
saveButtonClassName?: string;
|
||||||
|
isValid?: boolean;
|
||||||
}
|
}
|
||||||
export default class SettingItemMax extends React.PureComponent<Props> {
|
export default class SettingItemMax extends React.PureComponent<Props> {
|
||||||
settingList: React.RefObject<HTMLDivElement>;
|
settingList: React.RefObject<HTMLDivElement>;
|
||||||
@@ -120,31 +116,6 @@ export default class SettingItemMax extends React.PureComponent<Props> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let clientError = null;
|
|
||||||
if (this.props.clientError) {
|
|
||||||
clientError = (
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='col-sm-12 has-error'
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className='icon icon-alert-circle-outline'
|
|
||||||
role='img'
|
|
||||||
/>
|
|
||||||
<span className='sr-only'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='setting_item_max.error'
|
|
||||||
defaultMessage='Error'
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span id='clientError'>
|
|
||||||
{this.props.clientError}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let serverError = null;
|
let serverError = null;
|
||||||
if (this.props.serverError) {
|
if (this.props.serverError) {
|
||||||
serverError = (
|
serverError = (
|
||||||
@@ -193,7 +164,7 @@ export default class SettingItemMax extends React.PureComponent<Props> {
|
|||||||
<SaveButton
|
<SaveButton
|
||||||
defaultMessage={this.props.saveButtonText}
|
defaultMessage={this.props.saveButtonText}
|
||||||
saving={this.props.saving}
|
saving={this.props.saving}
|
||||||
disabled={this.props.saving}
|
disabled={this.props.saving || this.props.isValid === false}
|
||||||
onClick={this.handleSubmit}
|
onClick={this.handleSubmit}
|
||||||
btnClass={this.props.saveButtonClassName}
|
btnClass={this.props.saveButtonClassName}
|
||||||
/>
|
/>
|
||||||
@@ -267,7 +238,6 @@ export default class SettingItemMax extends React.PureComponent<Props> {
|
|||||||
role='alert'
|
role='alert'
|
||||||
>
|
>
|
||||||
{serverError}
|
{serverError}
|
||||||
{clientError}
|
|
||||||
</div>
|
</div>
|
||||||
{submit}
|
{submit}
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -141,8 +141,8 @@ describe('components/user_settings/general/UserSettingsGeneral', () => {
|
|||||||
<UserSettingsGeneral {...props}/>
|
<UserSettingsGeneral {...props}/>
|
||||||
</Provider>,
|
</Provider>,
|
||||||
);
|
);
|
||||||
expect(wrapper.find('#position').length).toBe(1);
|
expect(wrapper.find('#position').length).toBe(2);
|
||||||
expect(wrapper.find('#position').is('input')).toBeTruthy();
|
expect(wrapper.find('#position.Input').is('input')).toBeTruthy();
|
||||||
|
|
||||||
props.ldapPositionAttributeSet = true;
|
props.ldapPositionAttributeSet = true;
|
||||||
props.samlPositionAttributeSet = false;
|
props.samlPositionAttributeSet = false;
|
||||||
@@ -767,7 +767,7 @@ describe('components/user_settings/general/UserSettingsGeneral', () => {
|
|||||||
renderWithContext(<UserSettingsGeneral {...props}/>);
|
renderWithContext(<UserSettingsGeneral {...props}/>);
|
||||||
|
|
||||||
userEvent.type(screen.getByRole('textbox', {name: urlAttribute.name}), 'ftp://invalid-scheme');
|
userEvent.type(screen.getByRole('textbox', {name: urlAttribute.name}), 'ftp://invalid-scheme');
|
||||||
userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
userEvent.tab();
|
||||||
|
|
||||||
expect(await screen.findByText('Please enter a valid url.')).toBeInTheDocument();
|
expect(await screen.findByText('Please enter a valid url.')).toBeInTheDocument();
|
||||||
expect(saveCustomProfileAttribute).not.toHaveBeenCalled();
|
expect(saveCustomProfileAttribute).not.toHaveBeenCalled();
|
||||||
@@ -804,7 +804,7 @@ describe('components/user_settings/general/UserSettingsGeneral', () => {
|
|||||||
renderWithContext(<UserSettingsGeneral {...props}/>);
|
renderWithContext(<UserSettingsGeneral {...props}/>);
|
||||||
|
|
||||||
userEvent.type(screen.getByRole('textbox', {name: emailAttribute.name}), 'invalid-email');
|
userEvent.type(screen.getByRole('textbox', {name: emailAttribute.name}), 'invalid-email');
|
||||||
userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
userEvent.tab();
|
||||||
|
|
||||||
expect(await screen.findByText('Please enter a valid email address.')).toBeInTheDocument();
|
expect(await screen.findByText('Please enter a valid email address.')).toBeInTheDocument();
|
||||||
expect(saveCustomProfileAttribute).not.toHaveBeenCalled();
|
expect(saveCustomProfileAttribute).not.toHaveBeenCalled();
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {trackEvent} from 'actions/telemetry_actions.jsx';
|
|||||||
import SettingItem from 'components/setting_item';
|
import SettingItem from 'components/setting_item';
|
||||||
import SettingItemMax from 'components/setting_item_max';
|
import SettingItemMax from 'components/setting_item_max';
|
||||||
import SettingPicture from 'components/setting_picture';
|
import SettingPicture from 'components/setting_picture';
|
||||||
|
import Input from 'components/widgets/inputs/input/input';
|
||||||
import LoadingWrapper from 'components/widgets/loading/loading_wrapper';
|
import LoadingWrapper from 'components/widgets/loading/loading_wrapper';
|
||||||
|
|
||||||
import {AnnouncementBarMessages, AnnouncementBarTypes, AcceptedProfileImageTypes, Constants, ValidationErrors} from 'utils/constants';
|
import {AnnouncementBarMessages, AnnouncementBarTypes, AcceptedProfileImageTypes, Constants, ValidationErrors} from 'utils/constants';
|
||||||
@@ -183,7 +184,7 @@ type State = {
|
|||||||
sectionIsSaving: boolean;
|
sectionIsSaving: boolean;
|
||||||
showSpinner: boolean;
|
showSpinner: boolean;
|
||||||
resendStatus?: string;
|
resendStatus?: string;
|
||||||
clientError?: string | null;
|
pictureError?: string | null;
|
||||||
serverError?: string | {server_error_id: string; message: string};
|
serverError?: string | {server_error_id: string; message: string};
|
||||||
emailError?: string;
|
emailError?: string;
|
||||||
customAttributeValues: Record<string, string | string[]>;
|
customAttributeValues: Record<string, string | string[]>;
|
||||||
@@ -245,19 +246,6 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
const user = Object.assign({}, this.props.user);
|
const user = Object.assign({}, this.props.user);
|
||||||
const username = this.state.username.trim().toLowerCase();
|
const username = this.state.username.trim().toLowerCase();
|
||||||
|
|
||||||
const {formatMessage} = this.props.intl;
|
|
||||||
const usernameError = Utils.isValidUsername(username);
|
|
||||||
if (usernameError) {
|
|
||||||
let errObj;
|
|
||||||
if (usernameError.id === ValidationErrors.RESERVED_NAME) {
|
|
||||||
errObj = {clientError: formatMessage(holders.usernameReserved), serverError: ''};
|
|
||||||
} else {
|
|
||||||
errObj = {clientError: formatMessage(holders.usernameRestrictions, {min: Constants.MIN_USERNAME_LENGTH, max: Constants.MAX_USERNAME_LENGTH}), serverError: ''};
|
|
||||||
}
|
|
||||||
this.setState(errObj);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.username === username) {
|
if (user.username === username) {
|
||||||
this.updateSection('');
|
this.updateSection('');
|
||||||
return;
|
return;
|
||||||
@@ -310,34 +298,37 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
const confirmEmail = this.state.confirmEmail.trim().toLowerCase();
|
const confirmEmail = this.state.confirmEmail.trim().toLowerCase();
|
||||||
const currentPassword = this.state.currentPassword;
|
const currentPassword = this.state.currentPassword;
|
||||||
|
|
||||||
const {formatMessage} = this.props.intl;
|
|
||||||
|
|
||||||
if (email === user.email && (confirmEmail === '' || confirmEmail === user.email)) {
|
if (email === user.email && (confirmEmail === '' || confirmEmail === user.email)) {
|
||||||
this.updateSection('');
|
this.updateSection('');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (email === '' || !isEmail(email)) {
|
|
||||||
this.setState({emailError: formatMessage(holders.validEmail), clientError: '', serverError: ''});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (email !== confirmEmail) {
|
|
||||||
this.setState({emailError: formatMessage(holders.emailMatch), clientError: '', serverError: ''});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentPassword === '') {
|
|
||||||
this.setState({emailError: formatMessage(holders.emptyPassword), clientError: '', serverError: ''});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
user.email = email;
|
user.email = email;
|
||||||
user.password = currentPassword;
|
user.password = currentPassword;
|
||||||
trackEvent('settings', 'user_settings_update', {field: 'email'});
|
trackEvent('settings', 'user_settings_update', {field: 'email'});
|
||||||
this.submitUser(user, true);
|
this.submitUser(user, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
isEmailValid = () => {
|
||||||
|
const email = this.state.email.trim().toLowerCase();
|
||||||
|
const confirmEmail = this.state.confirmEmail.trim().toLowerCase();
|
||||||
|
const currentPassword = this.state.currentPassword;
|
||||||
|
|
||||||
|
if (email === '' || !isEmail(email)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (email !== confirmEmail) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPassword === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
submitUser = (user: UserProfile, emailUpdated: boolean) => {
|
submitUser = (user: UserProfile, emailUpdated: boolean) => {
|
||||||
const {formatMessage} = this.props.intl;
|
const {formatMessage} = this.props.intl;
|
||||||
this.setState({sectionIsSaving: true});
|
this.setState({sectionIsSaving: true});
|
||||||
@@ -367,7 +358,7 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
} else {
|
} else {
|
||||||
serverError = err;
|
serverError = err;
|
||||||
}
|
}
|
||||||
this.setState({serverError, emailError: '', clientError: '', sectionIsSaving: false});
|
this.setState({serverError, emailError: '', sectionIsSaving: false});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -384,7 +375,7 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
} else {
|
} else {
|
||||||
serverError = err;
|
serverError = err;
|
||||||
}
|
}
|
||||||
this.setState({serverError, emailError: '', clientError: '', sectionIsSaving: false});
|
this.setState({serverError, emailError: '', pictureError: '', sectionIsSaving: false});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -403,10 +394,10 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
const file = this.state.pictureFile;
|
const file = this.state.pictureFile;
|
||||||
|
|
||||||
if (!AcceptedProfileImageTypes.includes(file.type)) {
|
if (!AcceptedProfileImageTypes.includes(file.type)) {
|
||||||
this.setState({clientError: formatMessage(holders.validImage), serverError: ''});
|
this.setState({pictureError: formatMessage(holders.validImage), serverError: ''});
|
||||||
return;
|
return;
|
||||||
} else if (file.size > this.props.maxFileSize) {
|
} else if (file.size > this.props.maxFileSize) {
|
||||||
this.setState({clientError: formatMessage(holders.imageTooLarge), serverError: ''});
|
this.setState({pictureError: formatMessage(holders.imageTooLarge), serverError: ''});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,8 +433,6 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
submitAttribute = async (settings: string[]) => {
|
submitAttribute = async (settings: string[]) => {
|
||||||
const {formatMessage} = this.props.intl;
|
|
||||||
|
|
||||||
const attributeID = settings[0];
|
const attributeID = settings[0];
|
||||||
const attributeField = this.props.customProfileAttributeFields.find((field) => field.id === attributeID);
|
const attributeField = this.props.customProfileAttributeFields.find((field) => field.id === attributeID);
|
||||||
if (attributeField === undefined) {
|
if (attributeField === undefined) {
|
||||||
@@ -454,7 +443,7 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
if (typeof attributeValue === 'string' && attributeField.attrs && attributeField.attrs.value_type) {
|
if (typeof attributeValue === 'string' && attributeField.attrs && attributeField.attrs.value_type) {
|
||||||
if (attributeField.attrs.value_type === 'email') {
|
if (attributeField.attrs.value_type === 'email') {
|
||||||
if (attributeValue !== '' && !isEmail(attributeValue)) {
|
if (attributeValue !== '' && !isEmail(attributeValue)) {
|
||||||
this.setState({clientError: formatMessage(holders.validEmail), emailError: '', serverError: ''});
|
this.setState({emailError: '', serverError: ''});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -462,7 +451,7 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
if (attributeValue !== '') {
|
if (attributeValue !== '') {
|
||||||
const validURL = validHttpUrl(attributeValue);
|
const validURL = validHttpUrl(attributeValue);
|
||||||
if (!validURL) {
|
if (!validURL) {
|
||||||
this.setState({clientError: formatMessage(holders.validUrl), emailError: '', serverError: ''});
|
this.setState({emailError: '', serverError: ''});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let validLink = validURL.toString();
|
let validLink = validURL.toString();
|
||||||
@@ -487,7 +476,7 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
this.setState({customAttributeValues: {...this.state.customAttributeValues, ...data}});
|
this.setState({customAttributeValues: {...this.state.customAttributeValues, ...data}});
|
||||||
} else if (err) {
|
} else if (err) {
|
||||||
const serverError = err.message;
|
const serverError = err.message;
|
||||||
this.setState({serverError, emailError: '', clientError: '', sectionIsSaving: false});
|
this.setState({serverError, emailError: '', sectionIsSaving: false});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -529,7 +518,7 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
this.setState({pictureFile: e.target.files[0]});
|
this.setState({pictureFile: e.target.files[0]});
|
||||||
|
|
||||||
this.submitActive = true;
|
this.submitActive = true;
|
||||||
this.setState({clientError: null});
|
this.setState({pictureError: null});
|
||||||
} else {
|
} else {
|
||||||
this.setState({pictureFile: null});
|
this.setState({pictureFile: null});
|
||||||
}
|
}
|
||||||
@@ -564,7 +553,7 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
updateSection = (section: string) => {
|
updateSection = (section: string) => {
|
||||||
this.setState(Object.assign({}, this.setupInitialState(this.props), {clientError: '', serverError: '', emailError: '', sectionIsSaving: false}));
|
this.setState(Object.assign({}, this.setupInitialState(this.props), {pictureError: '', serverError: '', emailError: '', sectionIsSaving: false}));
|
||||||
this.submitActive = false;
|
this.submitActive = false;
|
||||||
this.props.updateSection(section);
|
this.props.updateSection(section);
|
||||||
};
|
};
|
||||||
@@ -647,15 +636,24 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<div className='col-sm-7'>
|
<div className='col-sm-7'>
|
||||||
<input
|
<Input
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
id='primaryEmail'
|
id='primaryEmail'
|
||||||
className='form-control'
|
name='primaryEmail'
|
||||||
type='email'
|
type='email'
|
||||||
onChange={this.updateEmail}
|
onChange={this.updateEmail}
|
||||||
maxLength={Constants.MAX_EMAIL_LENGTH}
|
maxLength={Constants.MAX_EMAIL_LENGTH}
|
||||||
value={this.state.email}
|
value={this.state.email}
|
||||||
aria-label={formatMessage({id: 'user.settings.general.newEmail', defaultMessage: 'New Email'})}
|
aria-label={formatMessage({id: 'user.settings.general.newEmail', defaultMessage: 'New Email'})}
|
||||||
|
validate={(value) => {
|
||||||
|
if (value === '' || !isEmail(value as string)) {
|
||||||
|
return {
|
||||||
|
type: 'error',
|
||||||
|
value: formatMessage(holders.validEmail),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -675,14 +673,23 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<div className='col-sm-7'>
|
<div className='col-sm-7'>
|
||||||
<input
|
<Input
|
||||||
id='confirmEmail'
|
id='confirmEmail'
|
||||||
className='form-control'
|
name='confirmEmail'
|
||||||
type='email'
|
type='email'
|
||||||
onChange={this.updateConfirmEmail}
|
onChange={this.updateConfirmEmail}
|
||||||
maxLength={Constants.MAX_EMAIL_LENGTH}
|
maxLength={Constants.MAX_EMAIL_LENGTH}
|
||||||
value={this.state.confirmEmail}
|
value={this.state.confirmEmail}
|
||||||
aria-label={formatMessage({id: 'user.settings.general.confirmEmail', defaultMessage: 'Confirm Email'})}
|
aria-label={formatMessage({id: 'user.settings.general.confirmEmail', defaultMessage: 'Confirm Email'})}
|
||||||
|
validate={(value) => {
|
||||||
|
if (this.state.email !== value) {
|
||||||
|
return {
|
||||||
|
type: 'error',
|
||||||
|
value: formatMessage(holders.emailMatch),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -702,13 +709,22 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<div className='col-sm-7'>
|
<div className='col-sm-7'>
|
||||||
<input
|
<Input
|
||||||
id='currentPassword'
|
id='currentPassword'
|
||||||
className='form-control'
|
name='currentPassword'
|
||||||
type='password'
|
type='password'
|
||||||
onChange={this.updateCurrentPassword}
|
onChange={this.updateCurrentPassword}
|
||||||
value={this.state.currentPassword}
|
value={this.state.currentPassword}
|
||||||
aria-label={formatMessage({id: 'user.settings.general.currentPassword', defaultMessage: 'Current Password'})}
|
aria-label={formatMessage({id: 'user.settings.general.currentPassword', defaultMessage: 'Current Password'})}
|
||||||
|
validate={(value) => {
|
||||||
|
if (value === '') {
|
||||||
|
return {
|
||||||
|
type: 'error',
|
||||||
|
value: formatMessage(holders.emptyPassword),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -838,8 +854,8 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
submit={submit}
|
submit={submit}
|
||||||
saving={this.state.sectionIsSaving}
|
saving={this.state.sectionIsSaving}
|
||||||
serverError={this.state.serverError}
|
serverError={this.state.serverError}
|
||||||
clientError={this.state.emailError}
|
|
||||||
updateSection={this.updateSection}
|
updateSection={this.updateSection}
|
||||||
|
isValid={this.isEmailValid()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -959,10 +975,10 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<div className='col-sm-7'>
|
<div className='col-sm-7'>
|
||||||
<input
|
<Input
|
||||||
id='firstName'
|
id='firstName'
|
||||||
|
name='firstName'
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className='form-control'
|
|
||||||
type='text'
|
type='text'
|
||||||
onChange={this.updateFirstName}
|
onChange={this.updateFirstName}
|
||||||
maxLength={Constants.MAX_FIRSTNAME_LENGTH}
|
maxLength={Constants.MAX_FIRSTNAME_LENGTH}
|
||||||
@@ -989,9 +1005,9 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<div className='col-sm-7'>
|
<div className='col-sm-7'>
|
||||||
<input
|
<Input
|
||||||
id='lastName'
|
id='lastName'
|
||||||
className='form-control'
|
name='lastName'
|
||||||
type='text'
|
type='text'
|
||||||
onChange={this.updateLastName}
|
onChange={this.updateLastName}
|
||||||
maxLength={Constants.MAX_LASTNAME_LENGTH}
|
maxLength={Constants.MAX_LASTNAME_LENGTH}
|
||||||
@@ -1042,7 +1058,6 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
submit={submit}
|
submit={submit}
|
||||||
saving={this.state.sectionIsSaving}
|
saving={this.state.sectionIsSaving}
|
||||||
serverError={this.state.serverError}
|
serverError={this.state.serverError}
|
||||||
clientError={this.state.clientError}
|
|
||||||
updateSection={this.updateSection}
|
updateSection={this.updateSection}
|
||||||
extraInfo={extraInfo}
|
extraInfo={extraInfo}
|
||||||
/>
|
/>
|
||||||
@@ -1125,10 +1140,10 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
>
|
>
|
||||||
<label className='col-sm-5 control-label'>{nicknameLabel}</label>
|
<label className='col-sm-5 control-label'>{nicknameLabel}</label>
|
||||||
<div className='col-sm-7'>
|
<div className='col-sm-7'>
|
||||||
<input
|
<Input
|
||||||
id='nickname'
|
id='nickname'
|
||||||
|
name='nickname'
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className='form-control'
|
|
||||||
type='text'
|
type='text'
|
||||||
onChange={this.updateNickname}
|
onChange={this.updateNickname}
|
||||||
value={this.state.nickname}
|
value={this.state.nickname}
|
||||||
@@ -1159,7 +1174,6 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
submit={submit}
|
submit={submit}
|
||||||
saving={this.state.sectionIsSaving}
|
saving={this.state.sectionIsSaving}
|
||||||
serverError={this.state.serverError}
|
serverError={this.state.serverError}
|
||||||
clientError={this.state.clientError}
|
|
||||||
updateSection={this.updateSection}
|
updateSection={this.updateSection}
|
||||||
extraInfo={extraInfo}
|
extraInfo={extraInfo}
|
||||||
/>
|
/>
|
||||||
@@ -1227,17 +1241,33 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
>
|
>
|
||||||
<label className='col-sm-5 control-label'>{usernameLabel}</label>
|
<label className='col-sm-5 control-label'>{usernameLabel}</label>
|
||||||
<div className='col-sm-7'>
|
<div className='col-sm-7'>
|
||||||
<input
|
<Input
|
||||||
id='username'
|
id='username'
|
||||||
|
name='username'
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
maxLength={Constants.MAX_USERNAME_LENGTH}
|
maxLength={Constants.MAX_USERNAME_LENGTH}
|
||||||
className='form-control'
|
|
||||||
type='text'
|
type='text'
|
||||||
onChange={this.updateUsername}
|
onChange={this.updateUsername}
|
||||||
value={this.state.username}
|
value={this.state.username}
|
||||||
autoCapitalize='off'
|
autoCapitalize='off'
|
||||||
onFocus={Utils.moveCursorToEnd}
|
onFocus={Utils.moveCursorToEnd}
|
||||||
aria-label={formatMessage({id: 'user.settings.general.username', defaultMessage: 'Username'})}
|
aria-label={formatMessage({id: 'user.settings.general.username', defaultMessage: 'Username'})}
|
||||||
|
validate={(value) => {
|
||||||
|
const usernameError = Utils.isValidUsername(value as string);
|
||||||
|
if (usernameError) {
|
||||||
|
if (usernameError.id === ValidationErrors.RESERVED_NAME) {
|
||||||
|
return {
|
||||||
|
type: 'error',
|
||||||
|
value: formatMessage(holders.usernameReserved),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: 'error',
|
||||||
|
value: formatMessage(holders.usernameRestrictions, {min: Constants.MIN_USERNAME_LENGTH, max: Constants.MAX_USERNAME_LENGTH}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
@@ -1271,9 +1301,9 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
submit={submit}
|
submit={submit}
|
||||||
saving={this.state.sectionIsSaving}
|
saving={this.state.sectionIsSaving}
|
||||||
serverError={this.state.serverError}
|
serverError={this.state.serverError}
|
||||||
clientError={this.state.clientError}
|
|
||||||
updateSection={this.updateSection}
|
updateSection={this.updateSection}
|
||||||
extraInfo={extraInfo}
|
extraInfo={extraInfo}
|
||||||
|
isValid={Utils.isValidUsername(this.state.username) === undefined}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1328,10 +1358,10 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
>
|
>
|
||||||
<label className='col-sm-5 control-label'>{positionLabel}</label>
|
<label className='col-sm-5 control-label'>{positionLabel}</label>
|
||||||
<div className='col-sm-7'>
|
<div className='col-sm-7'>
|
||||||
<input
|
<Input
|
||||||
id='position'
|
id='position'
|
||||||
|
name='position'
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className='form-control'
|
|
||||||
type='text'
|
type='text'
|
||||||
onChange={this.updatePosition}
|
onChange={this.updatePosition}
|
||||||
value={this.state.position}
|
value={this.state.position}
|
||||||
@@ -1363,7 +1393,6 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
submit={submit}
|
submit={submit}
|
||||||
saving={this.state.sectionIsSaving}
|
saving={this.state.sectionIsSaving}
|
||||||
serverError={this.state.serverError}
|
serverError={this.state.serverError}
|
||||||
clientError={this.state.clientError}
|
|
||||||
updateSection={this.updateSection}
|
updateSection={this.updateSection}
|
||||||
extraInfo={extraInfo}
|
extraInfo={extraInfo}
|
||||||
/>
|
/>
|
||||||
@@ -1450,6 +1479,31 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
let extraInfo: JSX.Element|string;
|
let extraInfo: JSX.Element|string;
|
||||||
let submit = null;
|
let submit = null;
|
||||||
|
|
||||||
|
const validate = () => {
|
||||||
|
if (attribute.attrs?.value_type === 'email') {
|
||||||
|
const value = this.state.customAttributeValues[attribute.id] as string;
|
||||||
|
if (value && !isEmail(value)) {
|
||||||
|
return {
|
||||||
|
type: 'error' as const,
|
||||||
|
value: formatMessage(holders.validEmail),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (attribute.attrs?.value_type === 'url') {
|
||||||
|
const value = this.state.customAttributeValues[attribute.id] as string;
|
||||||
|
if (value) {
|
||||||
|
const validURL = validHttpUrl(value);
|
||||||
|
if (!validURL) {
|
||||||
|
return {
|
||||||
|
type: 'error' as const,
|
||||||
|
value: formatMessage(holders.validUrl),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
if ((this.props.user.auth_service === Constants.LDAP_SERVICE && attribute.attrs?.ldap) ||
|
if ((this.props.user.auth_service === Constants.LDAP_SERVICE && attribute.attrs?.ldap) ||
|
||||||
(this.props.user.auth_service === Constants.SAML_SERVICE && attribute.attrs?.saml)) {
|
(this.props.user.auth_service === Constants.SAML_SERVICE && attribute.attrs?.saml)) {
|
||||||
extraInfo = (
|
extraInfo = (
|
||||||
@@ -1504,10 +1558,10 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
>
|
>
|
||||||
<label className='col-sm-5 control-label'>{attributeLabel}</label>
|
<label className='col-sm-5 control-label'>{attributeLabel}</label>
|
||||||
<div className='col-sm-7'>
|
<div className='col-sm-7'>
|
||||||
<input
|
<Input
|
||||||
id={sectionName}
|
id={sectionName}
|
||||||
|
name={sectionName}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className='form-control'
|
|
||||||
type={inputType}
|
type={inputType}
|
||||||
onChange={this.updateAttribute}
|
onChange={this.updateAttribute}
|
||||||
value={getDisplayValue(this.state.customAttributeValues[attribute.id]) as string}
|
value={getDisplayValue(this.state.customAttributeValues[attribute.id]) as string}
|
||||||
@@ -1515,6 +1569,7 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
autoCapitalize='off'
|
autoCapitalize='off'
|
||||||
onFocus={Utils.moveCursorToEnd}
|
onFocus={Utils.moveCursorToEnd}
|
||||||
aria-label={attribute.name}
|
aria-label={attribute.name}
|
||||||
|
validate={validate}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
@@ -1540,9 +1595,9 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
submit={submit}
|
submit={submit}
|
||||||
saving={this.state.sectionIsSaving}
|
saving={this.state.sectionIsSaving}
|
||||||
serverError={this.state.serverError}
|
serverError={this.state.serverError}
|
||||||
clientError={this.state.clientError}
|
|
||||||
updateSection={this.updateSection}
|
updateSection={this.updateSection}
|
||||||
extraInfo={extraInfo}
|
extraInfo={extraInfo}
|
||||||
|
isValid={validate() === undefined}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1638,7 +1693,7 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
|
|||||||
src={imgSrc}
|
src={imgSrc}
|
||||||
defaultImageSrc={Utils.defaultImageURLForUser(user.id)}
|
defaultImageSrc={Utils.defaultImageURLForUser(user.id)}
|
||||||
serverError={this.state.serverError}
|
serverError={this.state.serverError}
|
||||||
clientError={this.state.clientError}
|
clientError={this.state.pictureError}
|
||||||
updateSection={(e: React.MouseEvent) => {
|
updateSection={(e: React.MouseEvent) => {
|
||||||
this.updateSection('');
|
this.updateSection('');
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import ExternalLink from 'components/external_link';
|
|||||||
import SettingItem from 'components/setting_item';
|
import SettingItem from 'components/setting_item';
|
||||||
import SettingItemMax from 'components/setting_item_max';
|
import SettingItemMax from 'components/setting_item_max';
|
||||||
import ToggleModalButton from 'components/toggle_modal_button';
|
import ToggleModalButton from 'components/toggle_modal_button';
|
||||||
|
import Input from 'components/widgets/inputs/input/input';
|
||||||
|
|
||||||
import icon50 from 'images/icon50x50.png';
|
import icon50 from 'images/icon50x50.png';
|
||||||
import Constants from 'utils/constants';
|
import Constants from 'utils/constants';
|
||||||
@@ -75,7 +76,6 @@ type State = {
|
|||||||
currentPassword: string;
|
currentPassword: string;
|
||||||
newPassword: string;
|
newPassword: string;
|
||||||
confirmPassword: string;
|
confirmPassword: string;
|
||||||
passwordError: React.ReactNode;
|
|
||||||
serverError: string | null;
|
serverError: string | null;
|
||||||
tokenError: string;
|
tokenError: string;
|
||||||
savingPassword: boolean;
|
savingPassword: boolean;
|
||||||
@@ -93,7 +93,6 @@ export class SecurityTab extends React.PureComponent<Props, State> {
|
|||||||
currentPassword: '',
|
currentPassword: '',
|
||||||
newPassword: '',
|
newPassword: '',
|
||||||
confirmPassword: '',
|
confirmPassword: '',
|
||||||
passwordError: '',
|
|
||||||
serverError: '',
|
serverError: '',
|
||||||
tokenError: '',
|
tokenError: '',
|
||||||
authService: this.props.user.auth_service,
|
authService: this.props.user.auth_service,
|
||||||
@@ -123,41 +122,8 @@ export class SecurityTab extends React.PureComponent<Props, State> {
|
|||||||
const user = this.props.user;
|
const user = this.props.user;
|
||||||
const currentPassword = this.state.currentPassword;
|
const currentPassword = this.state.currentPassword;
|
||||||
const newPassword = this.state.newPassword;
|
const newPassword = this.state.newPassword;
|
||||||
const confirmPassword = this.state.confirmPassword;
|
|
||||||
|
|
||||||
if (currentPassword === '') {
|
if (!this.isPasswordValid()) {
|
||||||
this.setState({
|
|
||||||
passwordError: this.props.intl.formatMessage({
|
|
||||||
id: 'user.settings.security.currentPasswordError',
|
|
||||||
defaultMessage: 'Please enter your current password.',
|
|
||||||
}),
|
|
||||||
serverError: '',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {valid, error} = isValidPassword(
|
|
||||||
newPassword,
|
|
||||||
this.props.passwordConfig,
|
|
||||||
);
|
|
||||||
if (!valid && error) {
|
|
||||||
this.setState({
|
|
||||||
passwordError: error,
|
|
||||||
serverError: '',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newPassword !== confirmPassword) {
|
|
||||||
const defaultState = Object.assign(this.getDefaultState(), {
|
|
||||||
passwordError: this.props.intl.formatMessage({
|
|
||||||
id: 'user.settings.security.passwordMatchError',
|
|
||||||
defaultMessage:
|
|
||||||
'The new passwords you entered do not match.',
|
|
||||||
}),
|
|
||||||
serverError: '',
|
|
||||||
});
|
|
||||||
this.setState(defaultState);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,11 +146,30 @@ export class SecurityTab extends React.PureComponent<Props, State> {
|
|||||||
} else {
|
} else {
|
||||||
state.serverError = err;
|
state.serverError = err;
|
||||||
}
|
}
|
||||||
state.passwordError = '';
|
|
||||||
this.setState(state);
|
this.setState(state);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
isPasswordValid = () => {
|
||||||
|
if (this.state.currentPassword === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {valid, error} = isValidPassword(
|
||||||
|
this.state.newPassword,
|
||||||
|
this.props.passwordConfig,
|
||||||
|
);
|
||||||
|
if (!valid && error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.newPassword !== this.state.confirmPassword) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
updateCurrentPassword = (e: React.ChangeEvent<HTMLInputElement>) => {
|
updateCurrentPassword = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
this.setState({currentPassword: e.target.value});
|
this.setState({currentPassword: e.target.value});
|
||||||
};
|
};
|
||||||
@@ -233,7 +218,6 @@ export class SecurityTab extends React.PureComponent<Props, State> {
|
|||||||
newPassword: '',
|
newPassword: '',
|
||||||
confirmPassword: '',
|
confirmPassword: '',
|
||||||
serverError: null,
|
serverError: null,
|
||||||
passwordError: null,
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -268,10 +252,10 @@ export class SecurityTab extends React.PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<div className='col-sm-7'>
|
<div className='col-sm-7'>
|
||||||
<input
|
<Input
|
||||||
id='currentPassword'
|
id='currentPassword'
|
||||||
|
name='currentPassword'
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className='form-control'
|
|
||||||
type='password'
|
type='password'
|
||||||
onChange={this.updateCurrentPassword}
|
onChange={this.updateCurrentPassword}
|
||||||
value={this.state.currentPassword}
|
value={this.state.currentPassword}
|
||||||
@@ -279,6 +263,20 @@ export class SecurityTab extends React.PureComponent<Props, State> {
|
|||||||
id: 'user.settings.security.currentPassword',
|
id: 'user.settings.security.currentPassword',
|
||||||
defaultMessage: 'Current Password',
|
defaultMessage: 'Current Password',
|
||||||
})}
|
})}
|
||||||
|
validate={(value) => {
|
||||||
|
if (typeof value !== 'string' || value === '') {
|
||||||
|
return {
|
||||||
|
type: 'error' as const,
|
||||||
|
value: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.security.currentPasswordError'
|
||||||
|
defaultMessage='Please enter your current password.'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
@@ -298,9 +296,9 @@ export class SecurityTab extends React.PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<div className='col-sm-7'>
|
<div className='col-sm-7'>
|
||||||
<input
|
<Input
|
||||||
id='newPassword'
|
id='newPassword'
|
||||||
className='form-control'
|
name='newPassword'
|
||||||
type='password'
|
type='password'
|
||||||
onChange={this.updateNewPassword}
|
onChange={this.updateNewPassword}
|
||||||
value={this.state.newPassword}
|
value={this.state.newPassword}
|
||||||
@@ -308,6 +306,19 @@ export class SecurityTab extends React.PureComponent<Props, State> {
|
|||||||
id: 'user.settings.security.newPassword',
|
id: 'user.settings.security.newPassword',
|
||||||
defaultMessage: 'New Password',
|
defaultMessage: 'New Password',
|
||||||
})}
|
})}
|
||||||
|
validate={(value) => {
|
||||||
|
const {valid, error} = isValidPassword(
|
||||||
|
value as string,
|
||||||
|
this.props.passwordConfig,
|
||||||
|
);
|
||||||
|
if (!valid) {
|
||||||
|
return {
|
||||||
|
type: 'error' as const,
|
||||||
|
value: error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
@@ -327,9 +338,9 @@ export class SecurityTab extends React.PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<div className='col-sm-7'>
|
<div className='col-sm-7'>
|
||||||
<input
|
<Input
|
||||||
id='confirmPassword'
|
id='confirmPassword'
|
||||||
className='form-control'
|
name='confirmPassword'
|
||||||
type='password'
|
type='password'
|
||||||
onChange={this.updateConfirmPassword}
|
onChange={this.updateConfirmPassword}
|
||||||
value={this.state.confirmPassword}
|
value={this.state.confirmPassword}
|
||||||
@@ -337,6 +348,23 @@ export class SecurityTab extends React.PureComponent<Props, State> {
|
|||||||
id: 'user.settings.security.retypePassword',
|
id: 'user.settings.security.retypePassword',
|
||||||
defaultMessage: 'Retype New Password',
|
defaultMessage: 'Retype New Password',
|
||||||
})}
|
})}
|
||||||
|
validate={(value) => {
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (this.state.newPassword !== value) {
|
||||||
|
return {
|
||||||
|
type: 'error' as const,
|
||||||
|
value: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.security.passwordMatchError'
|
||||||
|
defaultMessage='The new passwords you entered do not match.'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
@@ -435,8 +463,8 @@ export class SecurityTab extends React.PureComponent<Props, State> {
|
|||||||
submit={submit}
|
submit={submit}
|
||||||
saving={this.state.savingPassword}
|
saving={this.state.savingPassword}
|
||||||
serverError={this.state.serverError}
|
serverError={this.state.serverError}
|
||||||
clientError={this.state.passwordError}
|
|
||||||
updateSection={this.handleUpdateSection}
|
updateSection={this.handleUpdateSection}
|
||||||
|
isValid={this.isPasswordValid()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElem
|
|||||||
clearable?: boolean;
|
clearable?: boolean;
|
||||||
clearableTooltipText?: string;
|
clearableTooltipText?: string;
|
||||||
onClear?: () => void;
|
onClear?: () => void;
|
||||||
|
validate?: (value: React.InputHTMLAttributes<HTMLInputElement | HTMLTextAreaElement>['value']) => CustomMessageInputType | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Input = React.forwardRef((
|
const Input = React.forwardRef((
|
||||||
@@ -75,6 +76,7 @@ const Input = React.forwardRef((
|
|||||||
onBlur,
|
onBlur,
|
||||||
onChange,
|
onChange,
|
||||||
onClear,
|
onClear,
|
||||||
|
validate,
|
||||||
...otherProps
|
...otherProps
|
||||||
}: InputProps,
|
}: InputProps,
|
||||||
ref?: React.Ref<HTMLInputElement | HTMLTextAreaElement>,
|
ref?: React.Ref<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
@@ -151,6 +153,13 @@ const Input = React.forwardRef((
|
|||||||
};
|
};
|
||||||
|
|
||||||
const validateInput = () => {
|
const validateInput = () => {
|
||||||
|
if (validate) {
|
||||||
|
const validationError = validate(value);
|
||||||
|
if (validationError) {
|
||||||
|
setCustomInputLabel(validationError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Only check for required field validation on blur
|
// Only check for required field validation on blur
|
||||||
// Length validation is handled through derived values in the render function
|
// Length validation is handled through derived values in the render function
|
||||||
if (required && (value === null || value === '')) {
|
if (required && (value === null || value === '')) {
|
||||||
|
|||||||
@@ -296,6 +296,19 @@
|
|||||||
margin-block-start: 24px;
|
margin-block-start: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Input_fieldset {
|
||||||
|
background: var(--center-channel-bg);
|
||||||
|
|
||||||
|
.Input_wrapper {
|
||||||
|
padding: 0 12px;
|
||||||
|
|
||||||
|
.Input {
|
||||||
|
height: 28px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.timezone-container {
|
.timezone-container {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user