diff --git a/e2e-tests/cypress/tests/integration/channels/accessibility/accessibility_account_settings_spec.js b/e2e-tests/cypress/tests/integration/channels/accessibility/accessibility_account_settings_spec.js
index c7ea056a69..94a44a509e 100644
--- a/e2e-tests/cypress/tests/integration/channels/accessibility/accessibility_account_settings_spec.js
+++ b/e2e-tests/cypress/tests/integration/channels/accessibility/accessibility_account_settings_spec.js
@@ -34,7 +34,7 @@ describe('Verify Accessibility Support in different sections in Settings and Pro
{key: 'desktop', label: 'Desktop Notifications', type: 'radio'},
{key: 'email', label: 'Email Notifications', type: 'radio'},
{key: 'push', label: 'Mobile Push Notifications', type: 'radio'},
- {key: 'keys', label: 'Words That Trigger Mentions', type: 'checkbox'},
+ {key: 'keysWithNotification', label: 'Keywords that trigger Notifications', type: 'checkbox'},
{key: 'comments', label: 'Reply notifications', type: 'radio'},
],
display: [
diff --git a/e2e-tests/cypress/tests/integration/channels/notifications/at_icon_still_shows_mentions_list_with_deactivated_triggers_spec.js b/e2e-tests/cypress/tests/integration/channels/notifications/at_icon_still_shows_mentions_list_with_deactivated_triggers_spec.js
index 44d4065051..c50a58c998 100644
--- a/e2e-tests/cypress/tests/integration/channels/notifications/at_icon_still_shows_mentions_list_with_deactivated_triggers_spec.js
+++ b/e2e-tests/cypress/tests/integration/channels/notifications/at_icon_still_shows_mentions_list_with_deactivated_triggers_spec.js
@@ -23,12 +23,12 @@ describe('Notifications', () => {
// # Open 'Settings' modal
cy.uiOpenSettingsModal().within(() => {
- // # Open 'Words That Trigger Mentions' setting and uncheck all the checkboxes
- cy.findByRole('heading', {name: 'Words That Trigger Mentions'}).should('be.visible').click();
+ // # Open 'Keywords that trigger Notifications' setting and uncheck all the checkboxes
+ cy.findByRole('heading', {name: 'Keywords that trigger Notifications'}).should('be.visible').click();
cy.findByRole('checkbox', {name: `Your case-sensitive first name "${otherUser.first_name}"`}).should('not.be.checked');
cy.findByRole('checkbox', {name: `Your non case-sensitive username "${otherUser.username}"`}).should('not.be.checked');
cy.findByRole('checkbox', {name: 'Channel-wide mentions "@channel", "@all", "@here"'}).click().should('not.be.checked');
- cy.findByRole('checkbox', {name: 'Other non case-sensitive words, separated by commas:'}).should('not.be.checked');
+ cy.findByRole('checkbox', {name: 'Other non case-sensitive words, press Tab or use commas to separate keywords:'}).should('not.be.checked');
// # Save then close the modal
cy.uiSaveAndClose();
diff --git a/e2e-tests/cypress/tests/integration/channels/notifications/at_mentions_spec.js b/e2e-tests/cypress/tests/integration/channels/notifications/at_mentions_spec.js
index 822e2de01a..716c0319a4 100644
--- a/e2e-tests/cypress/tests/integration/channels/notifications/at_mentions_spec.js
+++ b/e2e-tests/cypress/tests/integration/channels/notifications/at_mentions_spec.js
@@ -243,14 +243,16 @@ function setNotificationSettings(desiredSettings = {first: true, username: true,
// Navigate to settings modal
cy.uiOpenSettingsModal();
- // Notifications header should be visible
- cy.get('#notificationSettingsTitle').
- scrollIntoView().
+ // # Click on notifications tab
+ cy.findByRoleExtended('tab', {name: 'Notifications'}).
should('be.visible').
- and('contain', 'Notifications');
+ click();
+
+ // Notifications header should be visible
+ cy.findAllByText('Notifications').should('be.visible');
// Open up 'Words that trigger mentions' sub-section
- cy.get('#keysTitle').
+ cy.findByText('Keywords that trigger Notifications').
scrollIntoView().
click();
@@ -270,8 +272,8 @@ function setNotificationSettings(desiredSettings = {first: true, username: true,
// Set Custom field
if (desiredSettings.custom && desiredSettings.customText) {
cy.get('#notificationTriggerCustomText').
- clear().
- type(desiredSettings.customText);
+ type(desiredSettings.customText, {force: true}).
+ tab();
}
// Click “Save” and close modal
diff --git a/e2e-tests/cypress/tests/integration/channels/notifications/deselect_username_mention_trigger_spec.js b/e2e-tests/cypress/tests/integration/channels/notifications/deselect_username_mention_trigger_spec.js
index 25544935bb..93bd1e2355 100644
--- a/e2e-tests/cypress/tests/integration/channels/notifications/deselect_username_mention_trigger_spec.js
+++ b/e2e-tests/cypress/tests/integration/channels/notifications/deselect_username_mention_trigger_spec.js
@@ -29,14 +29,16 @@ describe('Notifications', () => {
// # Open 'Settings' modal
cy.uiOpenSettingsModal().within(() => {
- cy.get('#keysEdit').should('be.visible').click();
+ // # Open 'Keywords that trigger Notifications' setting
+ cy.findByRole('heading', {name: 'Keywords that trigger Notifications'}).should('be.visible').click();
// * As otherUser, ensure that 'Your non-case sensitive username' is not checked
- cy.get('#notificationTriggerUsername').should('not.be.checked');
+ cy.findByRole('checkbox', {name: `Your non case-sensitive username "${otherUser.username}"`}).should('not.be.checked');
- // # Close the modal
- cy.get('#accountSettingsHeader').find('button').should('be.visible').click();
+ // # Save then close the modal
+ cy.uiSaveAndClose();
});
+
cy.apiLogout();
// # Login as sysadmin
diff --git a/webapp/channels/src/components/user_settings/notifications/__snapshots__/user_settings_notifications.test.tsx.snap b/webapp/channels/src/components/user_settings/notifications/__snapshots__/user_settings_notifications.test.tsx.snap
new file mode 100644
index 0000000000..896baf81d5
--- /dev/null
+++ b/webapp/channels/src/components/user_settings/notifications/__snapshots__/user_settings_notifications.test.tsx.snap
@@ -0,0 +1,476 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`components/user_settings/display/UserSettingsDisplay should match snapshot 1`] = `
+Object {
+ "asFragment": [Function],
+ "baseElement":
+
+
+
+
+
+
+
+
+
+ Desktop Notifications
+
+
+
+
+
+
+ For all activity, without sound
+
+
+
+
+
+
+ Email Notifications
+
+
+
+
+
+
+ Email notifications are not enabled
+
+
+
+
+
+
+ Mobile Push Notifications
+
+
+
+
+
+
+ Never
+
+
+
+
+
+
+ Keywords that trigger Notifications
+
+
+
+
+
+
+ "@some-user"
+
+
+
+
+
+
+
+ ,
+ "container":
+
+
+
+
+
+
+
+
+ Desktop Notifications
+
+
+
+
+
+
+ For all activity, without sound
+
+
+
+
+
+
+ Email Notifications
+
+
+
+
+
+
+ Email notifications are not enabled
+
+
+
+
+
+
+ Mobile Push Notifications
+
+
+
+
+
+
+ Never
+
+
+
+
+
+
+ Keywords that trigger Notifications
+
+
+
+
+
+
+ "@some-user"
+
+
+
+
+
+
+
,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "replaceStoreState": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+ "updateStoreState": [Function],
+}
+`;
diff --git a/webapp/channels/src/components/user_settings/notifications/index.ts b/webapp/channels/src/components/user_settings/notifications/index.ts
index 8aff57468f..5f19f609b0 100644
--- a/webapp/channels/src/components/user_settings/notifications/index.ts
+++ b/webapp/channels/src/components/user_settings/notifications/index.ts
@@ -1,42 +1,37 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import type {ActionCreatorsMapObject, Dispatch} from 'redux';
+import {connect, type ConnectedProps} from 'react-redux';
import {updateMe} from 'mattermost-redux/actions/users';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences';
-import type {ActionFunc} from 'mattermost-redux/types/actions';
import {isCallsEnabled, isCallsRingingEnabledOnServer} from 'selectors/calls';
import type {GlobalState} from 'types/store';
import UserSettingsNotifications from './user_settings_notifications';
-import type {Props} from './user_settings_notifications';
-function mapStateToProps(state: GlobalState) {
+const mapStateToProps = (state: GlobalState) => {
const config = getConfig(state);
const sendPushNotifications = config.SendPushNotifications === 'true';
const enableAutoResponder = config.ExperimentalEnableAutomaticReplies === 'true';
-
return {
sendPushNotifications,
enableAutoResponder,
isCollapsedThreadsEnabled: isCollapsedThreadsEnabled(state),
isCallsRingingEnabled: isCallsEnabled(state, '0.17.0') && isCallsRingingEnabledOnServer(state),
};
-}
+};
-function mapDispatchToProps(dispatch: Dispatch) {
- return {
- actions: bindActionCreators, Props['actions']>({
- updateMe,
- }, dispatch),
- };
-}
+const mapDispatchToProps = {
+ updateMe,
+};
+
+const connector = connect(mapStateToProps, mapDispatchToProps);
+
+export type PropsFromRedux = ConnectedProps;
export default connect(mapStateToProps, mapDispatchToProps)(UserSettingsNotifications);
diff --git a/webapp/channels/src/components/user_settings/notifications/user_settings_notifications.scss b/webapp/channels/src/components/user_settings/notifications/user_settings_notifications.scss
new file mode 100644
index 0000000000..aea66ee015
--- /dev/null
+++ b/webapp/channels/src/components/user_settings/notifications/user_settings_notifications.scss
@@ -0,0 +1,5 @@
+.customKeywordsWithNotificationSubsection {
+ & .multiInput {
+ margin-block-start: 10px;
+ }
+}
diff --git a/webapp/channels/src/components/user_settings/notifications/user_settings_notifications.test.tsx b/webapp/channels/src/components/user_settings/notifications/user_settings_notifications.test.tsx
index 3428dc460f..810621e81c 100644
--- a/webapp/channels/src/components/user_settings/notifications/user_settings_notifications.test.tsx
+++ b/webapp/channels/src/components/user_settings/notifications/user_settings_notifications.test.tsx
@@ -1,89 +1,42 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
-import {shallow} from 'enzyme';
import React from 'react';
-import type {ComponentProps} from 'react';
-
-import type {UserNotifyProps} from '@mattermost/types/users';
+import {type IntlShape} from 'react-intl';
+import {renderWithFullContext, screen} from 'tests/react_testing_utils';
import {TestHelper} from 'utils/test_helper';
import UserSettingsNotifications from './user_settings_notifications';
describe('components/user_settings/display/UserSettingsDisplay', () => {
- const user = TestHelper.getUserMock({
- id: 'user_id',
- });
-
- const requiredProps: ComponentProps = {
- user,
+ const defaultProps = {
+ user: TestHelper.getUserMock({id: 'user_id'}),
updateSection: jest.fn(),
activeSection: '',
closeModal: jest.fn(),
collapseModal: jest.fn(),
- actions: {
- updateMe: jest.fn(() => Promise.resolve({})),
- },
- isCollapsedThreadsEnabled: false,
+ updateMe: jest.fn(() => Promise.resolve({})),
+ isCollapsedThreadsEnabled: true,
sendPushNotifications: false,
enableAutoResponder: false,
isCallsRingingEnabled: true,
+ intl: {} as IntlShape,
};
- test('should have called handleSubmit', async () => {
- const props = {...requiredProps, actions: {...requiredProps.actions}};
- const wrapper = shallow(
- ,
+ test('should match snapshot', () => {
+ const wrapper = renderWithFullContext(
+ ,
);
- await wrapper.instance().handleSubmit();
- expect(requiredProps.actions.updateMe).toHaveBeenCalled();
- });
-
- test('should have called handleSubmit', async () => {
- const updateMe = jest.fn(() => Promise.resolve({data: true}));
-
- const props = {...requiredProps, actions: {...requiredProps.actions, updateMe}};
- const wrapper = shallow(
- ,
- );
-
- await wrapper.instance().handleSubmit();
- expect(requiredProps.updateSection).toHaveBeenCalled();
- expect(requiredProps.updateSection).toHaveBeenCalledWith('');
- });
-
- test('should reset state when handleUpdateSection is called', () => {
- const newUpdateSection = jest.fn();
- const updateArg = 'unreadChannels';
- const props = {...requiredProps, updateSection: newUpdateSection, user: {...user, notify_props: {desktop: 'on'} as unknown as UserNotifyProps}};
- const wrapper = shallow(
- ,
- );
-
- wrapper.setState({isSaving: true, desktopActivity: 'off' as unknown as UserNotifyProps['desktop']});
- wrapper.instance().handleUpdateSection(updateArg);
-
- expect(wrapper.state('isSaving')).toEqual(false);
- expect(wrapper.state('desktopActivity')).toEqual('on');
- expect(newUpdateSection).toHaveBeenCalledTimes(1);
+ expect(wrapper).toMatchSnapshot();
});
test('should show reply notifications section when CRT off', () => {
- const wrapper = shallow(
- ,
- );
- expect(wrapper.exists('SettingItem[section="comments"]')).toBe(true);
- });
+ const props = {...defaultProps, isCollapsedThreadsEnabled: false};
- test('should not show reply notifications section when CRT on', () => {
- const wrapper = shallow(
- ,
- );
- expect(wrapper.exists('SettingItem[section="comments"]')).toBe(false);
+ renderWithFullContext();
+
+ expect(screen.getByText('Reply notifications')).toBeInTheDocument();
});
});
diff --git a/webapp/channels/src/components/user_settings/notifications/user_settings_notifications.tsx b/webapp/channels/src/components/user_settings/notifications/user_settings_notifications.tsx
index b4a2149fe8..56c0acbf98 100644
--- a/webapp/channels/src/components/user_settings/notifications/user_settings_notifications.tsx
+++ b/webapp/channels/src/components/user_settings/notifications/user_settings_notifications.tsx
@@ -5,8 +5,12 @@
import React from 'react';
import type {ChangeEvent, RefObject} from 'react';
-import {FormattedMessage} from 'react-intl';
+import type {WrappedComponentProps} from 'react-intl';
+import {FormattedMessage, injectIntl} from 'react-intl';
+import type {Styles as ReactSelectStyles, ValueType} from 'react-select';
+import CreatableReactSelect from 'react-select/creatable';
+import type {ServerError} from '@mattermost/types/errors';
import type {UserNotifyProps, UserProfile} from '@mattermost/types/users';
import type {ActionResult} from 'mattermost-redux/types/actions';
@@ -17,28 +21,34 @@ import SettingItemMax from 'components/setting_item_max';
import Constants, {NotificationLevels} from 'utils/constants';
import {t} from 'utils/i18n';
-import * as NotificationSounds from 'utils/notification_sounds';
-import {a11yFocus, localizeMessage, moveCursorToEnd} from 'utils/utils';
+import {stopTryNotificationRing} from 'utils/notification_sounds';
+import {a11yFocus} from 'utils/utils';
import DesktopNotificationSettings from './desktop_notification_setting/desktop_notification_settings';
import EmailNotificationSetting from './email_notification_setting';
import ManageAutoResponder from './manage_auto_responder/manage_auto_responder';
-export type Props = {
+import type {PropsFromRedux} from './index';
+import './user_settings_notifications.scss';
+
+const WHITE_SPACE_REGEX = /\s+/g;
+const COMMA_REGEX = /,/g;
+
+type MultiInputValue = {
+ label: string;
+ value: string;
+}
+
+type OwnProps = {
user: UserProfile;
updateSection: (section: string) => void;
activeSection: string;
closeModal: () => void;
collapseModal: () => void;
- sendPushNotifications: boolean;
- enableAutoResponder: boolean;
- actions: {
- updateMe: (user: UserProfile) => Promise;
- };
- isCollapsedThreadsEnabled: boolean;
- isCallsRingingEnabled: boolean;
}
+type Props = PropsFromRedux & OwnProps & WrappedComponentProps;
+
type State = {
enableEmail: UserNotifyProps['email'];
desktopActivity: UserNotifyProps['desktop'];
@@ -52,8 +62,9 @@ type State = {
desktopNotificationSound: UserNotifyProps['desktop_notification_sound'];
callsNotificationSound: UserNotifyProps['calls_notification_sound'];
usernameKey: boolean;
- customKeys: string;
- customKeysChecked: boolean;
+ isCustomKeysWithNotificationInputChecked: boolean;
+ customKeysWithNotification: MultiInputValue[];
+ customKeysWithNotificationInputValue: string;
firstNameKey: boolean;
channelKey: boolean;
autoResponderActive: boolean;
@@ -63,9 +74,7 @@ type State = {
serverError: string;
};
-function getNotificationsStateFromProps(props: Props): State {
- const user = props.user;
-
+function getDefaultStateFromProps(props: Props): State {
let desktop: UserNotifyProps['desktop'] = NotificationLevels.MENTION;
let desktopThreads: UserNotifyProps['desktop_threads'] = NotificationLevels.ALL;
let pushThreads: UserNotifyProps['push_threads'] = NotificationLevels.ALL;
@@ -79,87 +88,86 @@ function getNotificationsStateFromProps(props: Props): State {
let pushActivity: UserNotifyProps['push'] = NotificationLevels.MENTION;
let pushStatus: UserNotifyProps['push_status'] = Constants.UserStatuses.AWAY;
let autoResponderActive = false;
- let autoResponderMessage: UserNotifyProps['auto_responder_message'] = localizeMessage(
- 'user.settings.notifications.autoResponderDefault',
- 'Hello, I am out of office and unable to respond to messages.',
- );
+ let autoResponderMessage: UserNotifyProps['auto_responder_message'] = props.intl.formatMessage({
+ id: 'user.settings.notifications.autoResponderDefault',
+ defaultMessage: 'Hello, I am out of office and unable to respond to messages.',
+ });
- if (user.notify_props) {
- if (user.notify_props.desktop) {
- desktop = user.notify_props.desktop;
+ if (props.user.notify_props) {
+ if (props.user.notify_props.desktop) {
+ desktop = props.user.notify_props.desktop;
}
- if (user.notify_props.desktop_threads) {
- desktopThreads = user.notify_props.desktop_threads;
+ if (props.user.notify_props.desktop_threads) {
+ desktopThreads = props.user.notify_props.desktop_threads;
}
- if (user.notify_props.push_threads) {
- pushThreads = user.notify_props.push_threads;
+ if (props.user.notify_props.push_threads) {
+ pushThreads = props.user.notify_props.push_threads;
}
- if (user.notify_props.email_threads) {
- emailThreads = user.notify_props.email_threads;
+ if (props.user.notify_props.email_threads) {
+ emailThreads = props.user.notify_props.email_threads;
}
- if (user.notify_props.desktop_sound) {
- sound = user.notify_props.desktop_sound;
+ if (props.user.notify_props.desktop_sound) {
+ sound = props.user.notify_props.desktop_sound;
}
- if (user.notify_props.calls_desktop_sound) {
- callsSound = user.notify_props.calls_desktop_sound;
+ if (props.user.notify_props.calls_desktop_sound) {
+ callsSound = props.user.notify_props.calls_desktop_sound;
}
- if (user.notify_props.desktop_notification_sound) {
- desktopNotificationSound = user.notify_props.desktop_notification_sound;
+ if (props.user.notify_props.desktop_notification_sound) {
+ desktopNotificationSound = props.user.notify_props.desktop_notification_sound;
}
- if (user.notify_props.calls_notification_sound) {
- callsNotificationSound = user.notify_props.calls_notification_sound;
+ if (props.user.notify_props.calls_notification_sound) {
+ callsNotificationSound = props.user.notify_props.calls_notification_sound;
}
- if (user.notify_props.comments) {
- comments = user.notify_props.comments;
+ if (props.user.notify_props.comments) {
+ comments = props.user.notify_props.comments;
}
- if (user.notify_props.email) {
- enableEmail = user.notify_props.email;
+ if (props.user.notify_props.email) {
+ enableEmail = props.user.notify_props.email;
}
- if (user.notify_props.push) {
- pushActivity = user.notify_props.push;
+ if (props.user.notify_props.push) {
+ pushActivity = props.user.notify_props.push;
}
- if (user.notify_props.push_status) {
- pushStatus = user.notify_props.push_status;
+ if (props.user.notify_props.push_status) {
+ pushStatus = props.user.notify_props.push_status;
}
- if (user.notify_props.auto_responder_active) {
- autoResponderActive = user.notify_props.auto_responder_active === 'true';
+ if (props.user.notify_props.auto_responder_active) {
+ autoResponderActive = props.user.notify_props.auto_responder_active === 'true';
}
- if (user.notify_props.auto_responder_message) {
- autoResponderMessage = user.notify_props.auto_responder_message;
+ if (props.user.notify_props.auto_responder_message) {
+ autoResponderMessage = props.user.notify_props.auto_responder_message;
}
}
let usernameKey = false;
- let customKeys = '';
let firstNameKey = false;
let channelKey = false;
+ let isCustomKeysWithNotificationInputChecked = false;
+ const customKeysWithNotification: MultiInputValue[] = [];
- if (user.notify_props) {
- if (user.notify_props.mention_keys) {
- const keys = user.notify_props.mention_keys.split(',');
-
- if (keys.indexOf(user.username) === -1) {
- usernameKey = false;
- } else {
- usernameKey = true;
- keys.splice(keys.indexOf(user.username), 1);
- if (keys.indexOf(`@${user.username}`) !== -1) {
- keys.splice(keys.indexOf(`@${user.username}`), 1);
+ if (props.user.notify_props) {
+ if (props.user.notify_props.mention_keys) {
+ const mentionKeys = props.user.notify_props.mention_keys.split(',').filter((key) => key.length > 0);
+ mentionKeys.forEach((mentionKey) => {
+ // Remove username(s) from list of keys
+ if (mentionKey !== props.user.username && mentionKey !== `@${props.user.username}`) {
+ customKeysWithNotification.push({
+ label: mentionKey,
+ value: mentionKey,
+ });
}
- }
+ });
- customKeys = keys.join(',');
+ // Check if username is in list of keys, if so, set the checkbox to true
+ usernameKey = mentionKeys.includes(props.user.username);
+
+ // Check if there are any keys in the list, if so, set the checkbox of custom keys to true
+ isCustomKeysWithNotificationInputChecked = customKeysWithNotification.length > 0;
}
- if (user.notify_props.first_name) {
- firstNameKey = user.notify_props.first_name === 'true';
- }
-
- if (user.notify_props.channel) {
- channelKey = user.notify_props.channel === 'true';
- }
+ firstNameKey = props.user.notify_props?.first_name === 'true';
+ channelKey = props.user.notify_props?.channel === 'true';
}
return {
@@ -175,8 +183,9 @@ function getNotificationsStateFromProps(props: Props): State {
desktopNotificationSound,
callsNotificationSound,
usernameKey,
- customKeys,
- customKeysChecked: customKeys.length > 0,
+ customKeysWithNotification,
+ isCustomKeysWithNotificationInputChecked,
+ customKeysWithNotificationInputValue: '',
firstNameKey,
channelKey,
autoResponderActive,
@@ -187,9 +196,7 @@ function getNotificationsStateFromProps(props: Props): State {
};
}
-export default class NotificationsTab extends React.PureComponent {
- customCheckRef: RefObject;
- customMentionsRef: RefObject;
+class NotificationsTab extends React.PureComponent {
drawerRef: RefObject;
wrapperRef: RefObject;
@@ -200,14 +207,12 @@ export default class NotificationsTab extends React.PureComponent
constructor(props: Props) {
super(props);
- this.state = getNotificationsStateFromProps(props);
- this.customCheckRef = React.createRef();
- this.customMentionsRef = React.createRef();
+ this.state = getDefaultStateFromProps(props);
this.drawerRef = React.createRef();
this.wrapperRef = React.createRef();
}
- handleSubmit = (): void => {
+ handleSubmit = async () => {
const data: UserNotifyProps = {} as UserNotifyProps;
data.email = this.state.enableEmail;
data.desktop_sound = this.state.desktopSound;
@@ -221,47 +226,46 @@ export default class NotificationsTab extends React.PureComponent
data.push = this.state.pushActivity;
data.push_status = this.state.pushStatus;
data.comments = this.state.notifyCommentsLevel;
- data.auto_responder_active = this.state.autoResponderActive.toString() as UserNotifyProps['auto_responder_active'];
+ data.auto_responder_active = this.state.autoResponderActive ? 'true' : 'false';
data.auto_responder_message = this.state.autoResponderMessage;
+ data.first_name = this.state.firstNameKey ? 'true' : 'false';
+ data.channel = this.state.channelKey ? 'true' : 'false';
if (!data.auto_responder_message || data.auto_responder_message === '') {
- data.auto_responder_message = localizeMessage(
- 'user.settings.notifications.autoResponderDefault',
- 'Hello, I am out of office and unable to respond to messages.',
- );
+ data.auto_responder_message = this.props.intl.formatMessage({
+ id: 'user.settings.notifications.autoResponderDefault',
+ defaultMessage: 'Hello, I am out of office and unable to respond to messages.',
+ });
}
- const mentionKeys = [];
+ const mentionKeys: string[] = [];
if (this.state.usernameKey) {
mentionKeys.push(this.props.user.username);
}
-
- let stringKeys = mentionKeys.join(',');
- if (this.state.customKeys.length > 0 && this.state.customKeysChecked) {
- stringKeys += ',' + this.state.customKeys;
+ if (this.state.isCustomKeysWithNotificationInputChecked && this.state.customKeysWithNotification.length > 0) {
+ this.state.customKeysWithNotification.forEach((key) => {
+ mentionKeys.push(key.value);
+ });
}
-
- data.mention_keys = stringKeys;
- data.first_name = this.state.firstNameKey.toString() as UserNotifyProps['first_name'];
- data.channel = this.state.channelKey.toString() as UserNotifyProps['channel'];
+ data.mention_keys = mentionKeys.join(',');
this.setState({isSaving: true});
- NotificationSounds.stopTryNotificationRing();
+ stopTryNotificationRing();
- this.props.actions.updateMe({notify_props: data} as UserProfile).
- then(({data: result, error: err}) => {
- if (result) {
- this.handleUpdateSection('');
- this.setState(getNotificationsStateFromProps(this.props));
- } else if (err) {
- this.setState({serverError: err.message, isSaving: false});
- }
- });
+ const {data: updatedUser, error} = await this.props.updateMe({notify_props: data}) as ActionResult, ServerError>; // Fix in MM-46907
+ if (updatedUser) {
+ this.handleUpdateSection('');
+ this.setState(getDefaultStateFromProps(this.props));
+ } else if (error) {
+ this.setState({serverError: error.message, isSaving: false});
+ } else {
+ this.setState({serverError: '', isSaving: false});
+ }
};
handleCancel = (): void => {
- this.setState(getNotificationsStateFromProps(this.props));
- NotificationSounds.stopTryNotificationRing();
+ this.setState(getDefaultStateFromProps(this.props));
+ stopTryNotificationRing();
};
handleUpdateSection = (section: string): void => {
@@ -302,30 +306,92 @@ export default class NotificationsTab extends React.PureComponent
handleEmailRadio = (enableEmail: UserNotifyProps['email']): void => this.setState({enableEmail});
- updateUsernameKey = (val: boolean): void => this.setState({usernameKey: val});
+ handleChangeForUsernameKeyCheckbox = (event: ChangeEvent) => {
+ const {target: {checked}} = event;
+ this.setState({usernameKey: checked});
+ };
- updateFirstNameKey = (val: boolean): void => this.setState({firstNameKey: val});
+ handleChangeForFirstNameKeyCheckbox = (event: ChangeEvent) => {
+ const {target: {checked}} = event;
+ this.setState({firstNameKey: checked});
+ };
- updateChannelKey = (val: boolean): void => this.setState({channelKey: val});
+ handleChangeForChannelKeyCheckbox = (event: ChangeEvent) => {
+ const {target: {checked}} = event;
+ this.setState({channelKey: checked});
+ };
- updateCustomMentionKeys = (): void => {
- const checked = this.customCheckRef.current?.checked;
+ handleChangeForCustomKeysWithNotificationCheckbox = (event: ChangeEvent) => {
+ const {target: {checked}} = event;
+ this.setState({isCustomKeysWithNotificationInputChecked: checked});
+ };
- if (checked) {
- const text = this.customMentionsRef.current?.value || '';
+ handleChangeForCustomKeysWithNotificationInput = (values: ValueType<{ value: string }>) => {
+ if (values && Array.isArray(values) && values.length > 0) {
+ // Check the custom keys input checkbox when atleast a single key is entered
+ if (this.state.isCustomKeysWithNotificationInputChecked === false) {
+ this.setState({
+ isCustomKeysWithNotificationInputChecked: true,
+ });
+ }
- // remove all spaces and split string into individual keys
- this.setState({customKeys: text.replace(/ /g, ''), customKeysChecked: true});
+ const customKeysWithNotification = values.
+ map((value: MultiInputValue) => {
+ // Remove all spaces from the value
+ const formattedValue = value.value.trim().replace(WHITE_SPACE_REGEX, '');
+ return {value: formattedValue, label: formattedValue};
+ }).
+ filter((value) => value.value.length > 0);
+ this.setState({customKeysWithNotification});
} else {
- this.setState({customKeys: '', customKeysChecked: false});
+ this.setState({
+ isCustomKeysWithNotificationInputChecked: false,
+ customKeysWithNotification: [],
+ });
}
};
- onCustomChange = (): void => {
- if (this.customCheckRef.current) {
- this.customCheckRef.current.checked = true;
+ updateCustomKeysWithNotificationWithInputValue = (newValue: string) => {
+ const customKeysWithNotification = [
+ ...this.state.customKeysWithNotification,
+ {
+ value: newValue,
+ label: newValue,
+ },
+ ];
+
+ this.setState({
+ customKeysWithNotification,
+ customKeysWithNotificationInputValue: '', // Clear the input field
+ });
+
+ if (!this.state.isCustomKeysWithNotificationInputChecked) {
+ this.setState({isCustomKeysWithNotificationInputChecked: true});
+ }
+ };
+
+ handleOnKeydownForCustomKeysWithNotificationInput = (event: React.KeyboardEvent) => {
+ if (event.key === Constants.KeyCodes.COMMA[0] || event.key === Constants.KeyCodes.TAB[0]) {
+ const unsavedCustomKeyWithNotification = this.state.customKeysWithNotificationInputValue?.trim()?.replace(WHITE_SPACE_REGEX, '')?.replace(COMMA_REGEX, '') ?? '';
+ if (unsavedCustomKeyWithNotification.length > 0) {
+ this.updateCustomKeysWithNotificationWithInputValue(unsavedCustomKeyWithNotification);
+ }
+ }
+ };
+
+ handleChangeForCustomKeysWithNotificationInputValue = (value: string) => {
+ // Check if input contains comma, if so, add the value to the list of custom keys
+ if (!value.includes(Constants.KeyCodes.COMMA[0])) {
+ const formattedValue = value.trim().replace(WHITE_SPACE_REGEX, '');
+ this.setState({customKeysWithNotificationInputValue: formattedValue});
+ }
+ };
+
+ handleBlurForCustomKeysWithNotificationInput = () => {
+ const unsavedCustomKeyWithNotification = this.state.customKeysWithNotificationInputValue?.trim()?.replace(WHITE_SPACE_REGEX, '')?.replace(COMMA_REGEX, '') ?? '';
+ if (unsavedCustomKeyWithNotification.length > 0) {
+ this.updateCustomKeysWithNotificationWithInputValue(unsavedCustomKeyWithNotification);
}
- this.updateCustomMentionKeys();
};
createPushNotificationSection = () => {
@@ -544,7 +610,7 @@ export default class NotificationsTab extends React.PureComponent
}
max = (
return (
);
};
- createKeysSection = () => {
+ createKeywordsWithNotificationSection = () => {
const serverError = this.state.serverError;
const user = this.props.user;
- const active = this.props.activeSection === 'keys';
+ const isSectionExpanded = this.props.activeSection === 'keysWithNotification';
- let max = null;
- if (active) {
+ let expandedSection = null;
+ if (isSectionExpanded) {
const inputs = [];
if (user.first_name) {
- const handleUpdateFirstNameKey = (e: ChangeEvent): void => this.updateFirstNameKey(e.target.checked);
inputs.push(
@@ -648,11 +713,11 @@ export default class NotificationsTab extends React.PureComponent
id='notificationTriggerFirst'
type='checkbox'
checked={this.state.firstNameKey}
- onChange={handleUpdateFirstNameKey}
+ onChange={this.handleChangeForFirstNameKeyCheckbox}
/>
);
}
- const handleUpdateUsernameKey = (e: ChangeEvent): void => this.updateUsernameKey(e.target.checked);
inputs.push(
@@ -672,7 +736,7 @@ export default class NotificationsTab extends React.PureComponent
id='notificationTriggerUsername'
type='checkbox'
checked={this.state.usernameKey}
- onChange={handleUpdateUsernameKey}
+ onChange={this.handleChangeForUsernameKeyCheckbox}
/>
,
);
- const handleUpdateChannelKey = (e: ChangeEvent
): void => this.updateChannelKey(e.target.checked);
inputs.push(
@@ -695,7 +758,7 @@ export default class NotificationsTab extends React.PureComponent
id='notificationTriggerShouts'
type='checkbox'
checked={this.state.channelKey}
- onChange={handleUpdateChannelKey}
+ onChange={this.handleChangeForChannelKeyCheckbox}
/>
);
inputs.push(
-
+
,
);
const extraInfo = (
-
-
-
+
);
- max = (
+ expandedSection = (
);
}
- let keys = ['@' + user.username];
+ const selectedMentionKeys = ['@' + user.username];
if (this.state.firstNameKey) {
- keys.push(user.first_name);
+ selectedMentionKeys.push(user.first_name);
}
if (this.state.usernameKey) {
- keys.push(user.username);
+ selectedMentionKeys.push(user.username);
}
-
if (this.state.channelKey) {
- keys.push('@channel');
- keys.push('@all');
- keys.push('@here');
+ selectedMentionKeys.push('@channel');
+ selectedMentionKeys.push('@all');
+ selectedMentionKeys.push('@here');
}
- if (this.state.customKeys.length > 0) {
- keys = keys.concat(this.state.customKeys.split(','));
- }
-
- let describe: JSX.Element | string = '';
- for (let i = 0; i < keys.length; i++) {
- if (keys[i] !== '') {
- describe += '"' + keys[i] + '", ';
- }
- }
-
- if (describe.length > 0) {
- describe = describe.substring(0, describe.length - 2);
- } else {
- describe = (
-
- );
+ if (this.state.customKeysWithNotification.length > 0) {
+ const customKeysWithNotificationStringArray = this.state.customKeysWithNotification.map((key) => key.value);
+ selectedMentionKeys.push(...customKeysWithNotificationStringArray);
}
+ const collapsedDescription = selectedMentionKeys.filter((key) => key.trim().length !== 0).map((key) => `"${key}"`).join(', ');
return (
);
};
@@ -830,7 +885,10 @@ export default class NotificationsTab extends React.PureComponent
inputs.push(