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 438c25b569..a1b44c5856 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 @@ -31,12 +31,12 @@ describe('Verify Accessibility Support in different sections in Settings and Pro const settings = { notifications: [ - {key: 'desktop', label: 'Desktop Notifications', type: 'radio'}, - {key: 'email', label: 'Email Notifications', type: 'radio'}, - {key: 'push', label: 'Mobile Push Notifications', type: 'radio'}, - {key: 'keysWithNotification', label: 'Keywords That Trigger Notifications', type: 'checkbox'}, - {key: 'keysWithHighlight', label: 'Keywords That Get Highlighted (Without Notifications)', type: 'checkbox'}, - {key: 'comments', label: 'Reply Notifications', type: 'radio'}, + {key: 'desktopAndMobile', label: 'Desktop and mobile notifications', type: 'radio'}, + {key: 'desktopNotificationSound', label: 'Desktop notification sounds', type: 'radio'}, + {key: 'email', label: 'Email notifications', type: 'radio'}, + {key: 'keywordsAndMentions', label: 'Keywords that trigger notifications', type: 'checkbox'}, + {key: 'keywordsAndHighlight', label: 'Keywords that get highlighted (without notifications)', type: 'checkbox'}, + {key: 'replyNotifications', label: 'Reply notifications', type: 'radio'}, ], display: [ {key: 'theme', label: 'Theme', type: 'radio'}, @@ -147,18 +147,6 @@ describe('Verify Accessibility Support in different sections in Settings and Pro verifySettings(settings.advanced); }); - it('MM-T1481 Verify Correct Radio button behavior in Settings and Profile', () => { - cy.visit(url); - cy.postMessage('hello'); - cy.uiOpenSettingsModal(); - - cy.get('#notificationsButton').click(); - cy.get('#desktopEdit').click(); - cy.get('#desktopNotificationAllActivity').check().should('be.checked').tab().check(); - cy.get('#desktopNotificationMentions').should('be.checked').tab().check(); - cy.get('#desktopNotificationNever').should('be.checked'); - }); - it('MM-T1482 Input fields in Settings and Profile should read labels', () => { cy.visit(url); cy.postMessage('hello'); diff --git a/e2e-tests/cypress/tests/integration/channels/messaging/message_auto_response_spec.js b/e2e-tests/cypress/tests/integration/channels/messaging/message_auto_response_spec.js index 007b04dc74..af840df56e 100644 --- a/e2e-tests/cypress/tests/integration/channels/messaging/message_auto_response_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/messaging/message_auto_response_spec.js @@ -48,7 +48,7 @@ describe('Auto Response In DMs', () => { // # Open 'Settings' modal and view 'Notifications' cy.uiOpenSettingsModal().within(() => { // # Click on 'Edit' for 'Automatic Direct Message Replies - cy.get('#auto-responderEdit').should('exist').scrollIntoView().and('be.visible').click(); + cy.get('#autoResponderEdit').should('exist').scrollIntoView().and('be.visible').click(); // # Click on 'Enabled' checkbox cy.get('#autoResponderActive').should('be.visible').click(); 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 521d3f9133..0c17a7bfa2 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,8 +23,8 @@ describe('Notifications', () => { // # Open 'Settings' modal cy.uiOpenSettingsModal().within(() => { - // # Open 'Keywords That Trigger Notifications' setting and uncheck all the checkboxes - cy.findByRole('heading', {name: 'Keywords That Trigger Notifications'}).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'); 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 4982846810..c38d3d93fa 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 @@ -252,7 +252,7 @@ function setNotificationSettings(desiredSettings = {first: true, username: true, cy.findAllByText('Notifications').should('be.visible'); // Open up 'Words that trigger mentions' sub-section - cy.findByText('Keywords That Trigger Notifications'). + cy.findByText('Keywords that trigger notifications'). scrollIntoView(). click(); diff --git a/e2e-tests/cypress/tests/integration/channels/notifications/channel_links_show_as_links_spec.js b/e2e-tests/cypress/tests/integration/channels/notifications/channel_links_show_as_links_spec.js index 8751f3e8ff..5a30952ba2 100644 --- a/e2e-tests/cypress/tests/integration/channels/notifications/channel_links_show_as_links_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/notifications/channel_links_show_as_links_spec.js @@ -50,8 +50,8 @@ describe('Notifications', () => { // # Open 'Notifications' of 'Settings' modal cy.uiOpenSettingsModal().within(() => { - // # Open 'Email Notifications' setting and set to 'Immediately' - cy.findByRole('heading', {name: 'Email Notifications'}).should('be.visible').click(); + // # Open 'Email notifications' setting and set to 'Immediately' + cy.findByRole('heading', {name: 'Email notifications'}).should('be.visible').click(); cy.findByRole('radio', {name: 'Immediately'}).click().should('be.checked'); // # Save then close the 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 10bb5465fc..3d920b1e12 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,8 +29,8 @@ describe('Notifications', () => { // # Open 'Settings' modal cy.uiOpenSettingsModal().within(() => { - // # Open 'Keywords That Trigger Notifications' setting - cy.findByRole('heading', {name: 'Keywords That Trigger Notifications'}).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.findByRole('checkbox', {name: `Your non case-sensitive username "${otherUser.username}"`}).should('not.be.checked'); diff --git a/e2e-tests/cypress/tests/integration/channels/notifications/desktop_notifications_1_spec.js b/e2e-tests/cypress/tests/integration/channels/notifications/desktop_notifications_1_spec.js index 248849a1f0..e823387c89 100644 --- a/e2e-tests/cypress/tests/integration/channels/notifications/desktop_notifications_1_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/notifications/desktop_notifications_1_spec.js @@ -48,7 +48,7 @@ describe('Desktop notifications', () => { spyNotificationAs('withoutNotification', 'granted'); // # Ensure notifications are set up to fire a desktop notification if are mentioned. - changeDesktopNotificationAs('#desktopNotificationMentions'); + changeDesktopNotificationAs('mentions'); cy.apiGetChannelByName(testTeam.name, 'Off-Topic').then(({channel}) => { // # Logout the user @@ -100,7 +100,7 @@ describe('Desktop notifications', () => { const expected = '@' + otherUser.username + ': I\'m hungry :taco: Mattermost'; // # Ensure notifications are set up to fire a desktop notification if are mentioned. - changeDesktopNotificationAs('#desktopNotificationAllActivity'); + changeDesktopNotificationAs('all'); cy.apiGetChannelByName(testTeam.name, 'Off-Topic').then(({channel}) => { // # Have another user send a post. @@ -147,7 +147,7 @@ describe('Desktop notifications', () => { const expected = '@' + otherUser.username + ' did something new'; // # Ensure notifications are set up to fire a desktop notification for all activity. - changeDesktopNotificationAs('#desktopNotificationAllActivity'); + changeDesktopNotificationAs('all'); cy.apiGetChannelByName(testTeam.name, 'Off-Topic').then(({channel}) => { // # Have another user send a post. @@ -169,7 +169,7 @@ describe('Desktop notifications', () => { spyNotificationAs('withNotification', 'granted'); // # Ensure notifications are set up to fire a desktop notification if are mentioned - changeDesktopNotificationAs('#desktopNotificationMentions'); + changeDesktopNotificationAs('mentions'); // # Ensure display settings are set to "Show username" changeTeammateNameDisplayAs('#name_formatFormatA'); @@ -194,7 +194,7 @@ describe('Desktop notifications', () => { spyNotificationAs('withNotification', 'granted'); // # Ensure notifications are set up to fire a desktop notification if are mentioned - changeDesktopNotificationAs('#desktopNotificationMentions'); + changeDesktopNotificationAs('mentions'); // # Ensure display settings are set to "Show first and last name" changeTeammateNameDisplayAs('#name_formatFormatC'); @@ -235,7 +235,7 @@ describe('Desktop notifications', () => { spyNotificationAs('withNotification', 'granted'); // # Ensure notifications are set up to fire a desktop notification - changeDesktopNotificationAs('#desktopNotificationMentions'); + changeDesktopNotificationAs('mentions'); cy.apiGetChannelByName(testTeam.name, 'Off-Topic').then(({channel}) => { const messageWithoutNotification = 'message without notification'; @@ -271,7 +271,7 @@ describe('Desktop notifications', () => { spyNotificationAs('withNotification', 'granted'); // # Ensure notifications are set up to never fire a desktop notification - changeDesktopNotificationAs('#desktopNotificationNever'); + changeDesktopNotificationAs('nothing'); cy.apiGetChannelByName(testTeam.name, 'Off-Topic').then(({channel}) => { const messageWithNotification = `random message with mention @${testUser.username}`; @@ -297,14 +297,11 @@ describe('Desktop notifications', () => { // # Open settings modal cy.uiOpenSettingsModal().within(() => { - // # Click "Desktop" - cy.findByText('Desktop Notifications').should('be.visible').click(); + // # Click "Desktop sound notifications" + cy.findByText('Desktop notification sounds').should('be.visible').click(); // # Select sound off. - cy.get('#soundOff').check(); - - // # Ensure sound dropdown is not visible - cy.get('#displaySoundNotification').should('not.exist'); + cy.findByText('Message notification sound').click({force: true}); // # Click "Save" and close the modal cy.uiSaveAndClose(); diff --git a/e2e-tests/cypress/tests/integration/channels/notifications/desktop_notifications_2_spec.js b/e2e-tests/cypress/tests/integration/channels/notifications/desktop_notifications_2_spec.js index 12d9e3a4d3..5b081ecd2c 100644 --- a/e2e-tests/cypress/tests/integration/channels/notifications/desktop_notifications_2_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/notifications/desktop_notifications_2_spec.js @@ -43,7 +43,7 @@ describe('Desktop notifications', () => { it('MM-T885 Channel notifications: Desktop notifications mentions only', () => { // # Ensure notifications are set up to fire a desktop notification - changeDesktopNotificationAs('#desktopNotificationAllActivity'); + changeDesktopNotificationAs('all'); const messageWithNotification = `random message with mention @${testUser.username}`; const expected = `@${otherUser.username}: ${messageWithNotification}`; diff --git a/e2e-tests/cypress/tests/integration/channels/notifications/desktop_notifications_3_spec.js b/e2e-tests/cypress/tests/integration/channels/notifications/desktop_notifications_3_spec.js index 0a5942b6d5..a91ce32bda 100644 --- a/e2e-tests/cypress/tests/integration/channels/notifications/desktop_notifications_3_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/notifications/desktop_notifications_3_spec.js @@ -48,7 +48,7 @@ describe('Desktop notifications', () => { spyNotificationAs('withNotification', 'granted'); // # Ensure notifications are set up to fire a desktop notification if are mentioned - changeDesktopNotificationAs('#desktopNotificationMentions'); + changeDesktopNotificationAs('mentions'); // # Ensure display settings are set to "Show nickname if one exists, otherwise show first and last name" changeTeammateNameDisplayAs('#name_formatFormatB'); @@ -76,7 +76,7 @@ describe('Desktop notifications', () => { spyNotificationAs('withNotification', 'granted'); // # Ensure notifications are set up to fire a desktop notification if are mentioned - changeDesktopNotificationAs('#desktopNotificationMentions'); + changeDesktopNotificationAs('mentions'); // # Ensure display settings are set to "Show nickname if one exists, otherwise show first and last name" changeTeammateNameDisplayAs('#name_formatFormatB'); diff --git a/e2e-tests/cypress/tests/integration/channels/notifications/helper.js b/e2e-tests/cypress/tests/integration/channels/notifications/helper.js index c9e20cd8e6..1648bd9038 100644 --- a/e2e-tests/cypress/tests/integration/channels/notifications/helper.js +++ b/e2e-tests/cypress/tests/integration/channels/notifications/helper.js @@ -4,11 +4,21 @@ export function changeDesktopNotificationAs(category) { // # Open settings modal cy.uiOpenSettingsModal().within(() => { - // # Click "Desktop Notifications" - cy.findByText('Desktop Notifications').should('be.visible').click(); + // # Click "Desktop and mobile notifications" + cy.findByText('Desktop and mobile notifications').should('be.visible').click(); - // # Select category. - cy.get(category).check(); + cy.get('#sendDesktopNotificationsSection').should('exist').within(() => { + if (category === 'all') { + // # Click "For all All new messages" + cy.findByText('All new messages').should('be.visible').click({force: true}); + } else if (category === 'mentions') { + // # Click "For mentions" + cy.findByText('Mentions, direct messages, and group messages').should('be.visible').click({force: true}); + } else if (category === 'nothing') { + // # Click "For nothing" + cy.findByText('Nothing').should('be.visible').click({force: true}); + } + }); // # Click "Save" and close the modal cy.uiSaveAndClose(); diff --git a/e2e-tests/cypress/tests/integration/channels/notifications/notification_preferences_do_not_save_spec.js b/e2e-tests/cypress/tests/integration/channels/notifications/notification_preferences_do_not_save_spec.js index 9daf4ebea0..165030ea7d 100644 --- a/e2e-tests/cypress/tests/integration/channels/notifications/notification_preferences_do_not_save_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/notifications/notification_preferences_do_not_save_spec.js @@ -31,17 +31,17 @@ describe('Notifications', () => { }); it('MM-T555 Notification Preferences do not save when modal is closed without saving', () => { - // # Call function that clicks on Settings -> Notifications -> Email Notifications -> Send Email Notifications -> Never without saving + // # Call function that clicks on Settings -> Notifications -> Email notifications -> Send Email notifications -> Never without saving openSettingsAndClickEmailEdit(true); - // # Call function that checks Settings -> Notifications -> Email Notifications -> Send Email Notifications -> Never is not saved + // # Call function that checks Settings -> Notifications -> Email notifications -> Send Email notifications -> Never is not saved openSettingsAndClickEmailEdit(false); }); function openSettingsAndClickEmailEdit(shouldBeClicked = false) { // # Open 'Settings' modal cy.uiOpenSettingsModal().within(() => { - // # Click on the 'Edit' button next to Email Notifications + // # Click on the 'Edit' button next to Email notifications cy.get('#emailEdit').click(); if (shouldBeClicked) { diff --git a/e2e-tests/cypress/tests/integration/channels/notifications/reply_notifications_spec.js b/e2e-tests/cypress/tests/integration/channels/notifications/reply_notifications_spec.js index 0e9c2bdcc9..e52cdc5d30 100644 --- a/e2e-tests/cypress/tests/integration/channels/notifications/reply_notifications_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/notifications/reply_notifications_spec.js @@ -239,9 +239,7 @@ function setReplyNotificationsSetting(idToToggle) { and('contain', 'Notifications'); // Open up 'Reply Notifications' sub-section - cy.get('#commentsTitle'). - scrollIntoView(). - click(); + cy.findByText('Reply notifications').should('be.visible').scrollIntoView().click(); cy.get(idToToggle).check().should('be.checked'); diff --git a/e2e-tests/cypress/tests/integration/channels/notifications/reset_notification_when_settings_canceled_spec.js b/e2e-tests/cypress/tests/integration/channels/notifications/reset_notification_when_settings_canceled_spec.js index e14f97bee8..1db44c9d3d 100644 --- a/e2e-tests/cypress/tests/integration/channels/notifications/reset_notification_when_settings_canceled_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/notifications/reset_notification_when_settings_canceled_spec.js @@ -20,47 +20,27 @@ describe('Notifications', () => { }); it('MM-T5458 Notification sound modal selection should reset when settings canceled', () => { - // # Call function that clicks on Settings -> Notifications -> Desktop Notifications -> Notification sound -> Change sound -> Cancel -> Desktop Notifications - openSettingsAndChangeNotification(); - }); - - function openSettingsAndChangeNotification() { // # Open 'Settings' modal - cy.uiOpenSettingsModal().within(() => { - // # Navigate to Desktop Notification Settings - navigateToDesktopNotificationSettings(); + cy.uiOpenSettingsModal(); - // # Change Notification selection - setNotificationSound(); + // # Navigate to Desktop Notification Settings + cy.get('#desktopNotificationSoundEdit').should('be.visible').click(); - // # Click Cancel button - cy.uiCancelButton().click(); + // # Change Notification selection + cy.get('#messageNotificationSoundSelect').click(); - // # Navigate to Desktop Notification Settings - navigateToDesktopNotificationSettings(); - cy.uiClose(); - }); - } - - function setNotificationSound() { - // # Change Notification sound selection value is set to Down - cy.get('#displaySoundNotification').click(); + // # Select 'Bing' sound cy.findByText('Down').click(); - // * Verify Notification display changed to Down - verifyNotificationSelectionValue('Down'); - } + // # Click Cancel button to close the settings + cy.uiCancelButton().click(); - function navigateToDesktopNotificationSettings() { - // # Click on the 'Edit' button next to Desktop Notifications - cy.get('#desktopEdit').should('be.visible').click(); + // # Click on the 'Edit' button next to Desktop sound notification again + cy.get('#desktopNotificationSoundEdit').should('be.visible').click(); - // * Verify that the Notification sound is set to Bing - verifyNotificationSelectionValue('Bing'); - } + // * Verify that the Notification sound is set to Bing as we canceled the settings + cy.findByText('Bing').should('be.visible'); - function verifyNotificationSelectionValue(value) { - // * Verify that the Notification sound is set to certain value - cy.get('#displaySoundNotification').findByTestId('displaySoundNotificationValue').should('contain', value); - } + cy.uiClose(); + }); }); diff --git a/e2e-tests/cypress/tests/integration/channels/signin_authentication/desktop_session_expire_spec.js b/e2e-tests/cypress/tests/integration/channels/signin_authentication/desktop_session_expire_spec.js index 28d1a34854..6235fcff18 100644 --- a/e2e-tests/cypress/tests/integration/channels/signin_authentication/desktop_session_expire_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/signin_authentication/desktop_session_expire_spec.js @@ -34,7 +34,7 @@ describe('Authentication', () => { // # Open settings modal cy.uiOpenSettingsModal().within(() => { // Click "Desktop" - cy.findByText('Desktop Notifications').should('be.visible').click(); + cy.findByText('Desktop and mobile notifications').should('be.visible').click(); // # Set your desktop notifications to Never cy.get('#desktopNotificationNever').check(); diff --git a/webapp/channels/src/actions/notification_actions.jsx b/webapp/channels/src/actions/notification_actions.jsx index 9110966d63..18718e6316 100644 --- a/webapp/channels/src/actions/notification_actions.jsx +++ b/webapp/channels/src/actions/notification_actions.jsx @@ -102,13 +102,13 @@ export function sendDesktopNotification(post, msgProps) { notifyLevel = user?.notify_props?.desktop || NotificationLevels.ALL; } - if (channel.type === 'G' && channelNotifyProp === NotificationLevels.DEFAULT && user?.notify_props?.desktop === NotificationLevels.MENTION) { + if (channel?.type === 'G' && channelNotifyProp === NotificationLevels.DEFAULT && user?.notify_props?.desktop === NotificationLevels.MENTION) { notifyLevel = NotificationLevels.ALL; } if (notifyLevel === NotificationLevels.NONE) { return; - } else if (channel.type === 'G' && notifyLevel === NotificationLevels.MENTION) { + } else if (channel?.type === 'G' && notifyLevel === NotificationLevels.MENTION) { // Compose the whole text in the message, including interactive messages. let text = post.message; diff --git a/webapp/channels/src/components/__snapshots__/setting_item_max.test.tsx.snap b/webapp/channels/src/components/__snapshots__/setting_item_max.test.tsx.snap index 13701dc3a9..86050df07e 100644 --- a/webapp/channels/src/components/__snapshots__/setting_item_max.test.tsx.snap +++ b/webapp/channels/src/components/__snapshots__/setting_item_max.test.tsx.snap @@ -11,7 +11,7 @@ exports[`components/SettingItemMax should match snapshot 1`] = ` title
{ } const inputs = this.props.inputs; - let widthClass; - if (this.props.width === 'full') { - widthClass = 'col-sm-12'; - } else if (this.props.width === 'medium') { - widthClass = 'col-sm-10 col-sm-offset-2'; - } else { - widthClass = 'col-sm-9 col-sm-offset-3'; - } let title; if (this.props.title) { @@ -231,7 +224,12 @@ export default class SettingItemMax extends React.PureComponent { className={`section-max form-horizontal ${this.props.containerStyle}`} > {title} -
+
{ defaultMessage='Language' /> } - width='medium' submit={this.changeLanguage} saving={this.state.isSaving} inputs={[input]} diff --git a/webapp/channels/src/components/user_settings/display/manage_timezones/manage_timezones.tsx b/webapp/channels/src/components/user_settings/display/manage_timezones/manage_timezones.tsx index 73da03ce0c..37cf72ff4d 100644 --- a/webapp/channels/src/components/user_settings/display/manage_timezones/manage_timezones.tsx +++ b/webapp/channels/src/components/user_settings/display/manage_timezones/manage_timezones.tsx @@ -245,7 +245,6 @@ export default class ManageTimezones extends React.PureComponent { /> } containerStyle='timezone-container' - width='medium' submit={this.changeTimezone} saving={this.state.isSaving} inputs={inputs} diff --git a/webapp/channels/src/components/user_settings/display/user_settings_theme/user_settings_theme.tsx b/webapp/channels/src/components/user_settings/display/user_settings_theme/user_settings_theme.tsx index d423e2ba31..9ccf54571a 100644 --- a/webapp/channels/src/components/user_settings/display/user_settings_theme/user_settings_theme.tsx +++ b/webapp/channels/src/components/user_settings/display/user_settings_theme/user_settings_theme.tsx @@ -309,7 +309,7 @@ export default class ThemeSetting extends React.PureComponent { disableEnterSubmit={true} saving={this.state.isSaving} serverError={serverError} - width='full' + isFullWidth={true} updateSection={this.handleUpdateSection} /> ); 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 index 10fcea360b..a81cfb5a9f 100644 --- 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 @@ -34,7 +34,7 @@ Object { class="fa fa-angle-left" />
- Notification Settings + Notification settings

- Desktop Notifications + Desktop and mobile notifications

+
+
+
+

+ Desktop notification sounds +

+ +
+
+ No sound
- Email Notifications + Email notifications
-
-
-
-

- Keywords That Trigger Notifications -

- -
-
"@some-user"
@@ -228,15 +228,15 @@ Object { >

- Keywords That Get Highlighted (Without Notifications) + Keywords that get highlighted (without notifications)

- Notification Settings + Notification settings

- Desktop Notifications + Desktop and mobile notifications

+
+
+
+

+ Desktop notification sounds +

+ +
+
+ No sound
- Email Notifications + Email notifications
-
-
-
-

- Keywords That Trigger Notifications -

- -
-
"@some-user"
@@ -486,15 +486,15 @@ Object { >

- Keywords That Get Highlighted (Without Notifications) + Keywords that get highlighted (without notifications)

- Notification Settings + Notification settings

- Desktop Notifications + Desktop and mobile notifications

+
+
+
+

+ Desktop notification sounds +

+ +
+
+ No sound
- Email Notifications + Email notifications
-
-
-
-

- Keywords That Trigger Notifications -

- -
-
"@some-user"
@@ -806,9 +806,9 @@ Object { >

- Keywords That Get Highlighted (Without Notifications) + Keywords that get highlighted (without notifications)

None
@@ -870,7 +870,7 @@ Object { class="fa fa-angle-left" />
- Notification Settings + Notification settings

- Desktop Notifications + Desktop and mobile notifications

+
+
+
+

+ Desktop notification sounds +

+ +
+
+ No sound
- Email Notifications + Email notifications
-
-
-
-

- Keywords That Trigger Notifications -

- -
-
"@some-user"
@@ -1067,9 +1067,9 @@ Object { >

- Keywords That Get Highlighted (Without Notifications) + Keywords that get highlighted (without notifications)

None
@@ -1190,7 +1190,7 @@ Object { class="fa fa-angle-left" />
- Notification Settings + Notification settings

- Desktop Notifications + Desktop and mobile notifications

+
+
+
+

+ Desktop notification sounds +

+ +
+
+ No sound
- Email Notifications + Email notifications
-
-
-
-

- Keywords That Trigger Notifications -

- -
-
"@some-user"
@@ -1413,7 +1413,7 @@ Object { class="fa fa-angle-left" />
- Notification Settings + Notification settings

- Desktop Notifications + Desktop and mobile notifications

+
+
+
+

+ Desktop notification sounds +

+ +
+
+ No sound
- Email Notifications + Email notifications
-
-
-
-

- Keywords That Trigger Notifications -

- -
-
"@some-user"
diff --git a/webapp/channels/src/components/user_settings/notifications/desktop_and_mobile_notification_setting/__snapshots__/index.test.tsx.snap b/webapp/channels/src/components/user_settings/notifications/desktop_and_mobile_notification_setting/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..624a176664 --- /dev/null +++ b/webapp/channels/src/components/user_settings/notifications/desktop_and_mobile_notification_setting/__snapshots__/index.test.tsx.snap @@ -0,0 +1,359 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DesktopNotificationSettings should match snapshot, on max setting 1`] = ` +
+
+

+ Desktop and mobile notifications +

+
+
+
+
+ + Send notifications for: + +
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+ Offline +
+ +
+
+ +
+
+
+
+
+
+ + +
+
+
+
+
+`; + +exports[`DesktopNotificationSettings should match snapshot, on min setting 1`] = ` +
+
+
+

+ Desktop and mobile notifications +

+ +
+
+ Configure desktop and mobile settings +
+
+
+`; + +exports[`DesktopNotificationSettings should not show desktop thread notification checkbox when collapsed threads are not enabled 1`] = ` +
+
+

+ Desktop and mobile notifications +

+
+
+
+
+ + Send notifications for: + +
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ Offline +
+ +
+
+ +
+
+
+
+
+
+ + +
+
+
+
+
+`; diff --git a/webapp/channels/src/components/user_settings/notifications/desktop_and_mobile_notification_setting/index.test.tsx b/webapp/channels/src/components/user_settings/notifications/desktop_and_mobile_notification_setting/index.test.tsx new file mode 100644 index 0000000000..f0ce194938 --- /dev/null +++ b/webapp/channels/src/components/user_settings/notifications/desktop_and_mobile_notification_setting/index.test.tsx @@ -0,0 +1,312 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; + +import {renderWithContext, screen} from 'tests/react_testing_utils'; +import Constants, {NotificationLevels} from 'utils/constants'; + +import type {SelectOption, Props} from './index'; +import DesktopNotificationSettings, { + shouldShowDesktopThreadsSection, + shouldShowMobileThreadsSection, + getValueOfSendMobileNotificationForSelect, + getValueOfSendMobileNotificationWhenSelect, + shouldShowTriggerMobileNotificationsSection, +} from './index'; + +const validNotificationLevels = Object.values(NotificationLevels); + +describe('DesktopNotificationSettings', () => { + const baseProps: Props = { + active: true, + updateSection: jest.fn(), + onSubmit: jest.fn(), + onCancel: jest.fn(), + saving: false, + error: '', + setParentState: jest.fn(), + areAllSectionsInactive: false, + isCollapsedThreadsEnabled: true, + desktopActivity: NotificationLevels.DEFAULT, + pushActivity: NotificationLevels.DEFAULT, + pushStatus: Constants.UserStatuses.OFFLINE, + desktopThreads: NotificationLevels.DEFAULT, + pushThreads: NotificationLevels.DEFAULT, + sendPushNotifications: true, + desktopAndMobileSettingsDifferent: false, + }; + + test('should match snapshot, on max setting', () => { + const {container} = renderWithContext( + , + ); + + expect(container).toMatchSnapshot(); + }); + + test('should match snapshot, on min setting', () => { + const props = {...baseProps, active: false}; + const {container} = renderWithContext( + , + ); + + expect(container).toMatchSnapshot(); + }); + + test('should not show desktop thread notification checkbox when collapsed threads are not enabled', () => { + const props = {...baseProps, isCollapsedThreadsEnabled: false}; + const {container} = renderWithContext( + , + ); + + expect(screen.queryByText('Notify me about replies to threads I\'m following')).toBeNull(); + expect(container).toMatchSnapshot(); + }); + + test('should not show desktop thread notification checkbox when desktop is all', () => { + const props = {...baseProps, desktopActivity: NotificationLevels.ALL}; + renderWithContext( + , + ); + + expect(screen.queryByText('Notify me about replies to threads I\'m following')).toBeNull(); + }); + + test('should not show desktop thread notification checkbox when desktop is none', () => { + const props = {...baseProps, desktopActivity: NotificationLevels.NONE}; + renderWithContext( + , + ); + + expect(screen.queryByText('Notify me about replies to threads I\'m following')).toBeNull(); + }); + + test('should show desktop thread notification checkbox when desktop is mention', () => { + const props = {...baseProps, desktopActivity: NotificationLevels.MENTION}; + renderWithContext( + , + ); + + expect(screen.getByText('Notify me about replies to threads I\'m following')).toBeInTheDocument(); + }); + + test('should show mobile notification when checkbox for use different mobile settings is checked', () => { + const props = {...baseProps, desktopAndMobileSettingsDifferent: true}; + renderWithContext( + , + ); + + expect(screen.getByText('Send mobile notifications for:')).toBeInTheDocument(); + }); + + test('should not show mobile notification when checkbox for use different mobile settings is not checked', () => { + const props = {...baseProps, desktopAndMobileSettingsDifferent: false}; + renderWithContext( + , + ); + + expect(screen.queryByText('Send mobile notifications for:')).toBeNull(); + }); + + test('should shown notify me about mobile threads when mobile setting is mention and desktop setting is anything', () => { + const {rerender} = renderWithContext( + , + ); + + expect(screen.getByText('Notify me on mobile about replies to threads I\'m following')).toBeInTheDocument(); + + rerender( + , + ); + + expect(screen.getByText('Notify me on mobile about replies to threads I\'m following')).toBeInTheDocument(); + }); + + test('should not show notify me about mobile threads when mobile setting is anything other than mention', () => { + const {rerender} = renderWithContext( + , + ); + + expect(screen.queryByText('Notify me on mobile about replies to threads I\'m following')).toBeNull(); + + rerender( + , + ); + + expect(screen.queryByText('Notify me on mobile about replies to threads I\'m following')).toBeNull(); + }); + + test('should show trigger mobile notifications section when desktop setting is mention', () => { + const props = {...baseProps, desktopActivity: NotificationLevels.MENTION, desktopAndMobileSettingsDifferent: false}; + renderWithContext( + , + ); + + expect(screen.getByText('Trigger mobile notifications when I am:')).toBeInTheDocument(); + }); + + test('should not show trigger mobile notifications section when desktop setting is none', () => { + const props = {...baseProps, desktopActivity: NotificationLevels.NONE, desktopAndMobileSettingsDifferent: false}; + renderWithContext( + , + ); + + expect(screen.queryByText('Trigger mobile notifications when I am:')).toBeNull(); + }); +}); + +describe('shouldShowDesktopThreadsSection', () => { + test('should not show when collapsed threads are not enabled', () => { + expect(shouldShowDesktopThreadsSection(false, 'hello' as any)).toBe(false); + }); + + test('should not show when desktop setting is either none or all', () => { + expect(shouldShowDesktopThreadsSection(true, NotificationLevels.NONE)).toBe(false); + expect(shouldShowDesktopThreadsSection(true, NotificationLevels.ALL)).toBe(false); + }); + + test('should show when desktop setting is mention', () => { + expect(shouldShowDesktopThreadsSection(true, NotificationLevels.MENTION)).toBe(true); + expect(shouldShowDesktopThreadsSection(true, NotificationLevels.DEFAULT)).toBe(true); + }); + + test('should show by default when desktop setting is undefined', () => { + expect(shouldShowDesktopThreadsSection(true, undefined as any)).toBe(true); + }); +}); + +describe('shouldShowMobileThreadsSection', () => { + test('should return false if sendPushNotifications is false', () => { + const result = shouldShowMobileThreadsSection(false, true, true, 'any' as any); + expect(result).toBe(false); + }); + + test('should return false if isCollapsedThreadsEnabled is false', () => { + const result = shouldShowMobileThreadsSection(true, false, true, 'any' as any); + expect(result).toBe(false); + }); + + test('should return false if desktop and mobile settings are same', () => { + const result = shouldShowMobileThreadsSection(true, true, false, 'any' as any); + expect(result).toBe(false); + }); + + test('should return false if pushActivity is all', () => { + const result = shouldShowMobileThreadsSection(true, true, true, NotificationLevels.ALL); + expect(result).toBe(false); + }); + + test('should return false if pushActivity is none', () => { + const result = shouldShowMobileThreadsSection(true, true, true, NotificationLevels.NONE); + expect(result).toBe(false); + }); +}); + +describe('getValueOfSendMobileNotificationForSelect', () => { + test('should return the middle option when input is undefined', () => { + expect(getValueOfSendMobileNotificationForSelect(undefined as any)).not.toBeUndefined(); + + const result = getValueOfSendMobileNotificationForSelect(undefined as any) as SelectOption; + expect(result.value).toBe(NotificationLevels.MENTION); + }); + + test('should return the middle option when input is not a valid option', () => { + expect(getValueOfSendMobileNotificationForSelect('invalid' as any)).not.toBeUndefined(); + + const result = getValueOfSendMobileNotificationForSelect('invalid' as any) as SelectOption; + expect(result.value).toBe(NotificationLevels.MENTION); + }); + + test('should return the same option when input is a valid option', () => { + validNotificationLevels. + filter((level) => level !== NotificationLevels.DEFAULT). + forEach((level) => { + expect(getValueOfSendMobileNotificationForSelect(level)).not.toBeUndefined(); + + const result = getValueOfSendMobileNotificationForSelect(level) as SelectOption; + expect(result.value).toBe(level); + }); + }); +}); + +describe('shouldShowTriggerMobileNotificationsSection', () => { + test('should not show when push notifications are off', () => { + expect(shouldShowTriggerMobileNotificationsSection(false, NotificationLevels.ALL, NotificationLevels.ALL, true)).toBe(false); + }); + + test('should show if either of desktop or mobile settings are not defined', () => { + expect(shouldShowTriggerMobileNotificationsSection(true, undefined as any, NotificationLevels.ALL, true)).toBe(true); + expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.ALL, undefined as any, true)).toBe(true); + }); + + test('should show if desktop is either mention or all for same mobile setting', () => { + expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.MENTION, NotificationLevels.MENTION, false)).toBe(true); + expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.ALL, NotificationLevels.ALL, false)).toBe(true); + }); + + test('should not show if desktop is none for same mobile setting', () => { + expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.NONE, NotificationLevels.NONE, false)).toBe(false); + }); + + test('should show for any desktop setting if mobile setting is mention', () => { + expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.ALL, NotificationLevels.MENTION, true)).toBe(true); + expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.MENTION, NotificationLevels.MENTION, true)).toBe(true); + expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.NONE, NotificationLevels.MENTION, true)).toBe(true); + }); + + test('should not show for any desktop setting if mobile setting is none', () => { + expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.ALL, NotificationLevels.NONE, true)).toBe(false); + expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.MENTION, NotificationLevels.NONE, true)).toBe(false); + expect(shouldShowTriggerMobileNotificationsSection(true, NotificationLevels.NONE, NotificationLevels.NONE, true)).toBe(false); + }); +}); + +describe('getValueOfSendMobileNotificationWhenSelect', () => { + test('When input is undefined it should return the last option', () => { + expect(getValueOfSendMobileNotificationWhenSelect(undefined)).not.toBeUndefined(); + + const result = getValueOfSendMobileNotificationWhenSelect(undefined) as SelectOption; + expect(result.value).toBe(Constants.UserStatuses.OFFLINE); + }); + + test('when input is defined but is not a valid option it should return the last option', () => { + // We are purposely testing with an invalid value hence the 'any' + expect(getValueOfSendMobileNotificationWhenSelect('invalid' as any)).not.toBeUndefined(); + + const result = getValueOfSendMobileNotificationWhenSelect('invalid' as any) as SelectOption; + expect(result.value).toBe(Constants.UserStatuses.OFFLINE); + }); + + test('When input is a valid option it should return the same option', () => { + expect(getValueOfSendMobileNotificationWhenSelect(Constants.UserStatuses.ONLINE)).not.toBeUndefined(); + + const result = getValueOfSendMobileNotificationWhenSelect(Constants.UserStatuses.ONLINE) as SelectOption; + expect(result.value).toBe(Constants.UserStatuses.ONLINE); + + expect(getValueOfSendMobileNotificationWhenSelect(Constants.UserStatuses.AWAY)).not.toBeUndefined(); + + const result2 = getValueOfSendMobileNotificationWhenSelect(Constants.UserStatuses.AWAY) as SelectOption; + expect(result2.value).toBe(Constants.UserStatuses.AWAY); + }); +}); diff --git a/webapp/channels/src/components/user_settings/notifications/desktop_and_mobile_notification_setting/index.tsx b/webapp/channels/src/components/user_settings/notifications/desktop_and_mobile_notification_setting/index.tsx new file mode 100644 index 0000000000..6001f59d4b --- /dev/null +++ b/webapp/channels/src/components/user_settings/notifications/desktop_and_mobile_notification_setting/index.tsx @@ -0,0 +1,582 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {Fragment, useCallback, useEffect, useMemo, useRef, memo} from 'react'; +import type {ChangeEvent, ReactNode} from 'react'; +import {FormattedMessage} from 'react-intl'; +import ReactSelect from 'react-select'; +import type {ValueType, OptionsType} from 'react-select'; + +import type {UserNotifyProps} from '@mattermost/types/users'; + +import SettingItemMax from 'components/setting_item_max'; +import SettingItemMin from 'components/setting_item_min'; +import type SettingItemMinComponent from 'components/setting_item_min'; + +import Constants, {NotificationLevels, UserSettingsNotificationSections} from 'utils/constants'; + +import type {Props as UserSettingsNotificationsProps} from '../user_settings_notifications'; + +export type SelectOption = { + label: ReactNode; + value: string; +}; + +export type Props = { + active: boolean; + updateSection: (section: string) => void; + onSubmit: () => void; + onCancel: () => void; + saving: boolean; + error: string; + setParentState: (key: string, value: string | boolean) => void; + areAllSectionsInactive: boolean; + isCollapsedThreadsEnabled: boolean; + desktopActivity: UserNotifyProps['desktop']; + sendPushNotifications: UserSettingsNotificationsProps['sendPushNotifications']; + pushActivity: UserNotifyProps['push']; + pushStatus: UserNotifyProps['push_status']; + desktopThreads: UserNotifyProps['desktop_threads']; + pushThreads: UserNotifyProps['push_threads']; + desktopAndMobileSettingsDifferent: boolean; +}; + +function DesktopAndMobileNotificationSettings({ + active, + updateSection, + onSubmit, + onCancel, + saving, + error, + setParentState, + areAllSectionsInactive, + isCollapsedThreadsEnabled, + desktopActivity, + sendPushNotifications, + pushActivity, + pushStatus, + desktopThreads, + pushThreads, + desktopAndMobileSettingsDifferent, +}: Props) { + const editButtonRef = useRef(null); + const previousActiveRef = useRef(active); + + // Focus back on the edit button, after this section was closed after it was opened + useEffect(() => { + if (previousActiveRef.current && !active && areAllSectionsInactive) { + editButtonRef.current?.focus(); + } + + previousActiveRef.current = active; + }, [active, areAllSectionsInactive]); + + const handleChangeForSendDesktopNotificationsRadio = useCallback((event: ChangeEvent) => { + const value = event.target.value; + setParentState('desktopActivity', value); + }, [setParentState]); + + const handleChangeForDesktopThreadsCheckbox = useCallback((event: ChangeEvent) => { + const value = event.target.checked ? NotificationLevels.ALL : NotificationLevels.MENTION; + setParentState('desktopThreads', value); + }, [setParentState]); + + const handleChangeForDifferentMobileNotificationsCheckbox = useCallback((event: ChangeEvent) => { + const value = event.target.checked; + setParentState('desktopAndMobileSettingsDifferent', value); + }, [setParentState]); + + const handleChangeForSendMobileNotificationsSelect = useCallback((selectedOption: ValueType) => { + if (selectedOption && 'value' in selectedOption) { + setParentState('pushActivity', selectedOption.value); + } + }, [setParentState]); + + const handleChangeForMobileThreadsCheckbox = useCallback((event: ChangeEvent) => { + const value = event.target.checked ? NotificationLevels.ALL : NotificationLevels.MENTION; + setParentState('pushThreads', value); + }, [setParentState]); + + const handleChangeForTriggerMobileNotificationsSelect = useCallback((selectedOption: ValueType) => { + if (selectedOption && 'value' in selectedOption) { + setParentState('pushStatus', selectedOption.value); + } + }, [setParentState]); + + const maximizedSettingsInputs = useMemo(() => { + const maximizedSettingInputs = []; + + const sendDesktopNotificationsSection = ( +
+ + + + {optionsOfSendNotifications.map((optionOfSendNotifications) => ( +
+ +
+ ))} +
+ ); + maximizedSettingInputs.push(sendDesktopNotificationsSection); + + if (shouldShowDesktopThreadsSection(isCollapsedThreadsEnabled, desktopActivity)) { + const desktopThreadNotificationSection = ( + +
+
+ +
+
+ ); + maximizedSettingInputs.push(desktopThreadNotificationSection); + } + + if (sendPushNotifications) { + const differentMobileNotificationsSection = ( + +
+
+ +
+
+ ); + maximizedSettingInputs.push(differentMobileNotificationsSection); + } + + if (shouldShowSendMobileNotificationsSection(sendPushNotifications, desktopAndMobileSettingsDifferent)) { + const sendMobileNotificationsSection = ( + +
+ + +
+ ); + maximizedSettingInputs.push(sendMobileNotificationsSection); + } + + if (shouldShowMobileThreadsSection(sendPushNotifications, isCollapsedThreadsEnabled, desktopAndMobileSettingsDifferent, pushActivity)) { + const threadNotificationSection = ( + +
+
+ +
+
+ ); + maximizedSettingInputs.push(threadNotificationSection); + } + + if (shouldShowTriggerMobileNotificationsSection(sendPushNotifications, desktopActivity, pushActivity, desktopAndMobileSettingsDifferent)) { + const triggerMobileNotificationsSection = ( + +
+ + +
+ ); + maximizedSettingInputs.push(triggerMobileNotificationsSection); + } + + if (!sendPushNotifications) { + const disabledPushNotificationsSection = ( + <> +
+ + + ); + maximizedSettingInputs.push(disabledPushNotificationsSection); + } + + return maximizedSettingInputs; + }, + [ + desktopActivity, + handleChangeForSendDesktopNotificationsRadio, + isCollapsedThreadsEnabled, + desktopThreads, + handleChangeForDesktopThreadsCheckbox, + sendPushNotifications, + desktopAndMobileSettingsDifferent, + handleChangeForDifferentMobileNotificationsCheckbox, + pushActivity, + handleChangeForSendMobileNotificationsSelect, + pushThreads, + handleChangeForMobileThreadsCheckbox, + pushStatus, + handleChangeForTriggerMobileNotificationsSelect, + ]); + + function handleChangeForMaxSection(section: string) { + updateSection(section); + } + + function handleChangeForMinSection(section: string) { + updateSection(section); + onCancel(); + } + + if (active) { + return ( + + } + inputs={maximizedSettingsInputs} + submit={onSubmit} + saving={saving} + serverError={error} + updateSection={handleChangeForMaxSection} + /> + ); + } + + return ( + + } + describe={getCollapsedText(desktopActivity, pushActivity)} + section={UserSettingsNotificationSections.DESKTOP_AND_MOBILE} + updateSection={handleChangeForMinSection} + /> + ); +} + +function NoIndicatorSeparatorComponent() { + return null; +} + +const optionsOfSendNotifications = [ + { + label: ( + + ), + value: NotificationLevels.ALL, + }, + { + label: ( + + ), + value: NotificationLevels.MENTION, + }, + { + label: ( + + ), + value: NotificationLevels.NONE, + }, +]; + +export function shouldShowDesktopThreadsSection(isCollapsedThreadsEnabled: boolean, desktopActivity: UserNotifyProps['desktop']) { + if (!isCollapsedThreadsEnabled) { + return false; + } + + if (desktopActivity === NotificationLevels.ALL || desktopActivity === NotificationLevels.NONE) { + return false; + } + + return true; +} + +export function shouldShowMobileThreadsSection(sendPushNotifications: UserSettingsNotificationsProps['sendPushNotifications'], isCollapsedThreadsEnabled: boolean, desktopAndMobileSettingsDifferent: boolean, pushActivity: UserNotifyProps['push']) { + if (!sendPushNotifications) { + return false; + } + + if (!isCollapsedThreadsEnabled) { + return false; + } + + if (!desktopAndMobileSettingsDifferent) { + return false; + } + + if (pushActivity === NotificationLevels.ALL || pushActivity === NotificationLevels.NONE) { + return false; + } + + return true; +} + +function shouldShowSendMobileNotificationsSection(sendPushNotifications: UserSettingsNotificationsProps['sendPushNotifications'], desktopAndMobileSettingsDifferent: boolean) { + if (!sendPushNotifications) { + return false; + } + + if (desktopAndMobileSettingsDifferent) { + return true; + } + + return false; +} + +export function getValueOfSendMobileNotificationForSelect(pushActivity: UserNotifyProps['push']): ValueType { + if (!pushActivity) { + return optionsOfSendNotifications[1]; + } + + const option = optionsOfSendNotifications.find((option) => option.value === pushActivity); + if (!option) { + return optionsOfSendNotifications[1]; + } + + return option; +} + +export function shouldShowTriggerMobileNotificationsSection(sendPushNotifications: UserSettingsNotificationsProps['sendPushNotifications'], desktopActivity: UserNotifyProps['desktop'], pushActivity: UserNotifyProps['push'], desktopAndMobileSettingsDifferent: boolean): boolean { + if (!sendPushNotifications) { + return false; + } + + if (!desktopActivity || !pushActivity) { + return true; + } + + if (!desktopAndMobileSettingsDifferent) { + if (desktopActivity === NotificationLevels.NONE) { + return false; + } + return true; + } + + if (pushActivity === NotificationLevels.NONE) { + return false; + } + + return true; +} + +const optionsOfSendMobileNotificationsWhenSelect: OptionsType = [ + { + label: ( + + ), + value: Constants.UserStatuses.ONLINE, + }, + { + label: ( + + ), + value: Constants.UserStatuses.AWAY, + }, + { + label: ( + + ), + value: Constants.UserStatuses.OFFLINE, + }, +]; + +export function getValueOfSendMobileNotificationWhenSelect(pushStatus?: UserNotifyProps['push_status']): ValueType { + if (!pushStatus) { + return optionsOfSendMobileNotificationsWhenSelect[2]; + } + + const option = optionsOfSendMobileNotificationsWhenSelect.find((option) => option.value === pushStatus); + if (!option) { + return optionsOfSendMobileNotificationsWhenSelect[2]; + } + + return option; +} + +function getCollapsedText(desktopActivity: UserNotifyProps['desktop'], pushActivity: UserNotifyProps['push']): ReactNode { + if (desktopActivity === NotificationLevels.ALL) { + if (pushActivity === NotificationLevels.ALL) { + return ( + + ); + } else if (pushActivity === NotificationLevels.MENTION) { + return ( + + ); + } else if (pushActivity === NotificationLevels.NONE) { + return ( + + ); + } + } else if (desktopActivity === NotificationLevels.MENTION) { + if (pushActivity === NotificationLevels.ALL) { + return ( + + ); + } else if (pushActivity === NotificationLevels.MENTION) { + return ( + + ); + } else if (pushActivity === NotificationLevels.NONE) { + return ( + + ); + } + } else if (desktopActivity === NotificationLevels.NONE) { + if (pushActivity === NotificationLevels.ALL) { + return ( + + ); + } else if (pushActivity === NotificationLevels.MENTION) { + return ( + + ); + } else if (pushActivity === NotificationLevels.NONE) { + return ( + + ); + } + } + + return ( + + ); +} + +export default memo(DesktopAndMobileNotificationSettings); diff --git a/webapp/channels/src/components/user_settings/notifications/desktop_notification_setting/__snapshots__/desktop_notification_settings.test.tsx.snap b/webapp/channels/src/components/user_settings/notifications/desktop_notification_setting/__snapshots__/desktop_notification_settings.test.tsx.snap deleted file mode 100644 index 6434fbf70c..0000000000 --- a/webapp/channels/src/components/user_settings/notifications/desktop_notification_setting/__snapshots__/desktop_notification_settings.test.tsx.snap +++ /dev/null @@ -1,1166 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`components/user_settings/notifications/DesktopNotificationSettings should match snapshot, on buildMaximizedSetting 1`] = ` - -
- - - -
- -
-
-
- -
-
-
- -
-
- -
-
-
-
- - - -
- -
-
-
- -
-
-
- -
-
-
, - ] - } - saving={false} - section="" - serverError="" - submit={[MockFunction]} - title={ - - } - updateSection={[Function]} -/> -`; - -exports[`components/user_settings/notifications/DesktopNotificationSettings should match snapshot, on buildMaximizedSetting 2`] = ` - -
- - - -
- -
-
-
- -
-
-
- -
-
- -
-
-
-
, - ] - } - saving={false} - section="" - serverError="" - submit={[MockFunction]} - title={ - - } - updateSection={[Function]} -/> -`; - -exports[`components/user_settings/notifications/DesktopNotificationSettings should match snapshot, on buildMinimizedSetting 1`] = ` - - } - section="desktop" - title={ - - } - updateSection={[Function]} -/> -`; - -exports[`components/user_settings/notifications/DesktopNotificationSettings should match snapshot, on buildMinimizedSetting 2`] = ` - - } - section="desktop" - title={ - - } - updateSection={[Function]} -/> -`; - -exports[`components/user_settings/notifications/DesktopNotificationSettings should match snapshot, on max setting 1`] = ` - -
- - - -
- -
-
-
- -
-
-
- -
-
- -
-
-
-
- - - -
- -
-
-
- -
-
-
- -
-
-
, - ] - } - saving={false} - section="" - serverError="" - submit={[MockFunction]} - title={ - - } - updateSection={[Function]} -/> -`; - -exports[`components/user_settings/notifications/DesktopNotificationSettings should match snapshot, on max setting with Calls enabled 1`] = ` - -
- - - -
- -
-
-
- -
-
-
- -
-
- -
-
-
-
- - - -
- -
-
-
- -
-
-
- -
-
- -
-
- - - -
- -
-
-
- -
-
-
-
-
, - ] - } - saving={false} - section="" - serverError="" - submit={[MockFunction]} - title={ - - } - updateSection={[Function]} -/> -`; - -exports[`components/user_settings/notifications/DesktopNotificationSettings should match snapshot, on max setting with Calls enabled, calls sound true 1`] = ` - -
- - - -
- -
-
-
- -
-
-
- -
-
- -
-
-
-
- - - -
- -
-
-
- -
-
-
- -
-
- -
-
- - - -
- -
-
-
- -
-
-
- -
-
-
-
, - ] - } - saving={false} - section="" - serverError="" - submit={[MockFunction]} - title={ - - } - updateSection={[Function]} -/> -`; - -exports[`components/user_settings/notifications/DesktopNotificationSettings should match snapshot, on max setting with sound enabled 1`] = ` - -
- - - -
- -
-
-
- -
-
-
- -
-
- -
-
-
-
- - - -
- -
-
-
- -
-
-
- -
-
- -
-
-
, - ] - } - saving={false} - section="" - serverError="" - submit={[MockFunction]} - title={ - - } - updateSection={[Function]} -/> -`; - -exports[`components/user_settings/notifications/DesktopNotificationSettings should match snapshot, on min setting 1`] = ` - - } - section="desktop" - title={ - - } - updateSection={[Function]} -/> -`; diff --git a/webapp/channels/src/components/user_settings/notifications/desktop_notification_setting/desktop_notification_settings.test.tsx b/webapp/channels/src/components/user_settings/notifications/desktop_notification_setting/desktop_notification_settings.test.tsx deleted file mode 100644 index ed6fae5791..0000000000 --- a/webapp/channels/src/components/user_settings/notifications/desktop_notification_setting/desktop_notification_settings.test.tsx +++ /dev/null @@ -1,155 +0,0 @@ -// 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 {NotificationLevels} from 'utils/constants'; - -import DesktopNotificationSettings from './desktop_notification_settings'; - -jest.mock('utils/notification_sounds', () => { - const original = jest.requireActual('utils/notification_sounds'); - return { - ...original, - hasSoundOptions: jest.fn(() => true), - }; -}); - -describe('components/user_settings/notifications/DesktopNotificationSettings', () => { - const baseProps: ComponentProps = { - active: true, - updateSection: jest.fn(), - onSubmit: jest.fn(), - onCancel: jest.fn(), - saving: false, - error: '', - setParentState: jest.fn(), - areAllSectionsInactive: false, - isCollapsedThreadsEnabled: false, - activity: NotificationLevels.MENTION, - threads: NotificationLevels.ALL, - sound: 'false', - callsSound: 'false', - selectedSound: 'Bing', - callsSelectedSound: 'Dynamic', - isCallsRingingEnabled: false, - }; - - test('should match snapshot, on max setting', () => { - const wrapper = shallow( - , - ); - - expect(wrapper).toMatchSnapshot(); - }); - - test('should match snapshot, on max setting with sound enabled', () => { - const props = {...baseProps, sound: 'true'}; - const wrapper = shallow( - , - ); - - expect(wrapper).toMatchSnapshot(); - }); - - test('should match snapshot, on max setting with Calls enabled', () => { - const props = {...baseProps, isCallsRingingEnabled: true}; - const wrapper = shallow( - , - ); - - expect(wrapper).toMatchSnapshot(); - }); - - test('should match snapshot, on max setting with Calls enabled, calls sound true', () => { - const props = {...baseProps, isCallsRingingEnabled: true, callsSound: 'true'}; - const wrapper = shallow( - , - ); - - expect(wrapper).toMatchSnapshot(); - }); - - test('should match snapshot, on min setting', () => { - const props = {...baseProps, active: false}; - const wrapper = shallow( - , - ); - - expect(wrapper).toMatchSnapshot(); - }); - - test('should call props.updateSection and props.onCancel on handleMinUpdateSection', () => { - const props = {...baseProps, updateSection: jest.fn(), onCancel: jest.fn()}; - const wrapper = shallow( - , - ); - - wrapper.instance().handleMinUpdateSection(''); - expect(props.updateSection).toHaveBeenCalledTimes(1); - expect(props.updateSection).toHaveBeenCalledWith(''); - expect(props.onCancel).toHaveBeenCalledTimes(1); - expect(props.onCancel).toHaveBeenCalledWith(); - - wrapper.instance().handleMinUpdateSection('desktop'); - expect(props.updateSection).toHaveBeenCalledTimes(2); - expect(props.updateSection).toHaveBeenCalledWith('desktop'); - expect(props.onCancel).toHaveBeenCalledTimes(2); - expect(props.onCancel).toHaveBeenCalledWith(); - }); - - test('should call props.updateSection on handleMaxUpdateSection', () => { - const props = {...baseProps, updateSection: jest.fn()}; - const wrapper = shallow( - , - ); - - wrapper.instance().handleMaxUpdateSection(''); - expect(props.updateSection).toHaveBeenCalledTimes(1); - expect(props.updateSection).toHaveBeenCalledWith(''); - - wrapper.instance().handleMaxUpdateSection('desktop'); - expect(props.updateSection).toHaveBeenCalledTimes(2); - expect(props.updateSection).toHaveBeenCalledWith('desktop'); - }); - - test('should call props.setParentState on handleOnChange', () => { - const props = {...baseProps, setParentState: jest.fn()}; - const wrapper = shallow( - , - ); - - wrapper.instance().handleOnChange({ - currentTarget: {getAttribute: (key: string) => { - return {'data-key': 'dataKey', 'data-value': 'dataValue'}[key]; - }}, - } as unknown as React.ChangeEvent); - - expect(props.setParentState).toHaveBeenCalledTimes(1); - expect(props.setParentState).toHaveBeenCalledWith('dataKey', 'dataValue'); - }); - - test('should match snapshot, on buildMaximizedSetting', () => { - const wrapper = shallow( - , - ); - - expect(wrapper.instance().buildMaximizedSetting()).toMatchSnapshot(); - - wrapper.setProps({activity: NotificationLevels.NONE}); - expect(wrapper.instance().buildMaximizedSetting()).toMatchSnapshot(); - }); - - test('should match snapshot, on buildMinimizedSetting', () => { - const wrapper = shallow( - , - ); - - expect(wrapper.instance().buildMinimizedSetting()).toMatchSnapshot(); - - wrapper.setProps({activity: NotificationLevels.NONE}); - expect(wrapper.instance().buildMinimizedSetting()).toMatchSnapshot(); - }); -}); diff --git a/webapp/channels/src/components/user_settings/notifications/desktop_notification_setting/desktop_notification_settings.tsx b/webapp/channels/src/components/user_settings/notifications/desktop_notification_setting/desktop_notification_settings.tsx deleted file mode 100644 index 423e42f7ae..0000000000 --- a/webapp/channels/src/components/user_settings/notifications/desktop_notification_setting/desktop_notification_settings.tsx +++ /dev/null @@ -1,547 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {type ChangeEvent, type RefObject, type ReactNode} from 'react'; -import {FormattedMessage} from 'react-intl'; -import ReactSelect, {type ValueType} from 'react-select'; - -import SettingItemMax from 'components/setting_item_max'; -import SettingItemMin from 'components/setting_item_min'; -import type SettingItemMinComponent from 'components/setting_item_min'; - -import {NotificationLevels} from 'utils/constants'; -import * as NotificationSounds from 'utils/notification_sounds'; -import {a11yFocus} from 'utils/utils'; - -type SelectedOption = { - label: string; - value: string; -}; - -type Props = { - active: boolean; - updateSection: (section: string) => void; - onSubmit: () => void; - onCancel: () => void; - saving: boolean; - error: string; - setParentState: (key: string, value: string | boolean) => void; - areAllSectionsInactive: boolean; - isCollapsedThreadsEnabled: boolean; - activity: string; - threads?: string; - sound: string; - callsSound: string; - selectedSound: string; - callsSelectedSound: string; - isCallsRingingEnabled: boolean; -}; - -type State = { - selectedOption: SelectedOption; - callsSelectedOption: SelectedOption; - blurDropdown: boolean; -}; - -export default class DesktopNotificationSettings extends React.PureComponent { - dropdownSoundRef: RefObject; - callsDropdownRef: RefObject; - editButtonRef: RefObject; - - constructor(props: Props) { - super(props); - - this.state = { - selectedOption: {value: props.selectedSound, label: props.selectedSound}, - callsSelectedOption: {value: props.callsSelectedSound, label: props.callsSelectedSound}, - blurDropdown: false, - }; - - this.dropdownSoundRef = React.createRef(); - this.callsDropdownRef = React.createRef(); - this.editButtonRef = React.createRef(); - } - - focusEditButton(): void { - this.editButtonRef.current?.focus(); - } - - handleMinUpdateSection = (section: string): void => { - this.props.updateSection(section); - this.props.onCancel(); - }; - - handleMaxUpdateSection = (section: string): void => this.props.updateSection(section); - - handleOnChange = (e: ChangeEvent): void => { - const key = e.currentTarget.getAttribute('data-key'); - const value = e.currentTarget.getAttribute('data-value'); - if (key && value) { - this.props.setParentState(key, value); - a11yFocus(e.currentTarget); - } - if (key === 'callsDesktopSound' && value === 'false') { - NotificationSounds.stopTryNotificationRing(); - } - }; - - handleThreadsOnChange = (e: ChangeEvent): void => { - const value = e.target.checked ? NotificationLevels.ALL : NotificationLevels.MENTION; - this.props.setParentState('desktopThreads', value); - }; - - setDesktopNotificationSound: ReactSelect['onChange'] = (selectedOption: ValueType): void => { - if (selectedOption && 'value' in selectedOption) { - this.props.setParentState('desktopNotificationSound', selectedOption.value); - this.setState({selectedOption}); - NotificationSounds.tryNotificationSound(selectedOption.value); - } - }; - - setCallsNotificationRing: ReactSelect['onChange'] = (selectedOption: ValueType): void => { - if (selectedOption && 'value' in selectedOption) { - this.props.setParentState('callsNotificationSound', selectedOption.value); - this.setState({callsSelectedOption: selectedOption}); - NotificationSounds.tryNotificationRing(selectedOption.value); - } - }; - - blurDropdown(): void { - if (!this.state.blurDropdown) { - this.setState({blurDropdown: true}); - if (this.dropdownSoundRef.current) { - this.dropdownSoundRef.current.blur(); - } - if (this.callsDropdownRef.current) { - this.callsDropdownRef.current.blur(); - } - } - } - - buildMaximizedSetting = (): JSX.Element => { - const inputs = []; - - const activityRadio = [false, false, false]; - if (this.props.activity === NotificationLevels.MENTION) { - activityRadio[1] = true; - } else if (this.props.activity === NotificationLevels.NONE) { - activityRadio[2] = true; - } else { - activityRadio[0] = true; - } - - let soundSection; - let notificationSelection; - let threadsNotificationSelection; - let callsSection; - let callsNotificationSelection; - if (this.props.activity !== NotificationLevels.NONE) { - const soundRadio = [false, false]; - if (this.props.sound === 'false') { - soundRadio[1] = true; - } else { - soundRadio[0] = true; - } - - if (this.props.sound === 'true') { - const sounds = Array.from(NotificationSounds.notificationSounds.keys()); - const options = sounds.map((sound) => { - return {value: sound, label: sound}; - }); - - notificationSelection = (
-
{props.children}
}} - />
); - } - - if (this.props.isCallsRingingEnabled) { - const callsSoundRadio = [false, false]; - if (this.props.callsSound === 'false') { - callsSoundRadio[1] = true; - } else { - callsSoundRadio[0] = true; - } - - if (this.props.callsSound === 'true') { - const callsSounds = Array.from(NotificationSounds.callsNotificationSounds.keys()); - const callsOptions = callsSounds.map((sound) => { - return {value: sound, label: sound}; - }); - - callsNotificationSelection = (
-
{props.children}
}} - />
); - } - - callsSection = ( - <> -
-
- - - -
- -
-
-
- -
-
- {callsNotificationSelection} -
- - ); - } - - if (NotificationSounds.hasSoundOptions()) { - soundSection = ( -
- - - -
- -
-
-
- -
-
- {notificationSelection} -
- -
-
- ); - } else { - soundSection = ( -
- - - -
- -
- ); - } - } - - if (this.props.isCollapsedThreadsEnabled && NotificationLevels.MENTION === this.props.activity) { - threadsNotificationSelection = ( - <> -
- - - -
- -
-
-
- -
-
-
- - ); - } - - inputs.push( -
-
- - - -
- -
-
-
- -
-
-
- -
-
- -
-
-
- {threadsNotificationSelection} - {soundSection} - {callsSection} -
, - ); - - return ( - - } - inputs={inputs} - submit={this.props.onSubmit} - saving={this.props.saving} - serverError={this.props.error} - updateSection={this.handleMaxUpdateSection} - /> - ); - }; - - buildMinimizedSetting = () => { - const hasSoundOption = NotificationSounds.hasSoundOptions(); - let collapsedDescription: ReactNode = null; - - if (this.props.activity === NotificationLevels.MENTION) { - if (hasSoundOption && this.props.sound !== 'false') { - collapsedDescription = ( - - ); - } else if (hasSoundOption && this.props.sound === 'false') { - collapsedDescription = ( - - ); - } else { - collapsedDescription = ( - - ); - } - } else if (this.props.activity === NotificationLevels.NONE) { - collapsedDescription = ( - - ); - } else { - if (hasSoundOption && this.props.sound !== 'false') { //eslint-disable-line no-lonely-if - collapsedDescription = ( - - ); - } else if (hasSoundOption && this.props.sound === 'false') { - collapsedDescription = ( - - ); - } else { - collapsedDescription = ( - - ); - } - } - - return ( - - } - describe={collapsedDescription} - section={'desktop'} - updateSection={this.handleMinUpdateSection} - /> - ); - }; - - componentDidUpdate(prevProps: Props) { - this.blurDropdown(); - if (prevProps.active && !this.props.active && this.props.areAllSectionsInactive) { - this.focusEditButton(); - } - if (this.props.selectedSound !== prevProps.selectedSound) { - this.setState({selectedOption: {value: this.props.selectedSound, label: this.props.selectedSound}}); - } - if (this.props.callsSelectedSound !== prevProps.callsSelectedSound) { - this.setState({callsSelectedOption: {value: this.props.callsSelectedSound, label: this.props.callsSelectedSound}}); - } - } - - render() { - if (this.props.active) { - return this.buildMaximizedSetting(); - } - - return this.buildMinimizedSetting(); - } -} diff --git a/webapp/channels/src/components/user_settings/notifications/desktop_notification_sounds_setting/index.tsx b/webapp/channels/src/components/user_settings/notifications/desktop_notification_sounds_setting/index.tsx new file mode 100644 index 0000000000..b8cfb1c81d --- /dev/null +++ b/webapp/channels/src/components/user_settings/notifications/desktop_notification_sounds_setting/index.tsx @@ -0,0 +1,493 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import type {ChangeEvent, ReactNode} from 'react'; +import React, {memo, useEffect, useRef, Fragment, useMemo, useCallback} from 'react'; +import {FormattedMessage, useIntl} from 'react-intl'; +import type {ValueType} from 'react-select'; +import ReactSelect from 'react-select'; + +import type {UserNotifyProps} from '@mattermost/types/users'; + +import SettingItemMax from 'components/setting_item_max'; +import SettingItemMin from 'components/setting_item_min'; +import type SettingItemMinComponent from 'components/setting_item_min'; + +import {UserSettingsNotificationSections} from 'utils/constants'; +import { + callsNotificationSounds, + notificationSounds, + stopTryNotificationRing, + tryNotificationSound, + tryNotificationRing, +} from 'utils/notification_sounds'; + +import type {Props as UserSettingsNotificationsProps} from '../user_settings_notifications'; + +export type SelectOption = { + value: string; + label: ReactNode; +}; + +export type Props = { + active: boolean; + updateSection: (section: string) => void; + onSubmit: () => void; + onCancel: () => void; + saving: boolean; + error: string; + setParentState: (key: string, value: string | boolean) => void; + areAllSectionsInactive: boolean; + desktopSound: UserNotifyProps['desktop_sound']; + desktopNotificationSound: UserNotifyProps['desktop_notification_sound']; + isCallsRingingEnabled: UserSettingsNotificationsProps['isCallsRingingEnabled']; + callsDesktopSound: UserNotifyProps['calls_desktop_sound']; + callsNotificationSound: UserNotifyProps['calls_notification_sound']; +}; + +function DesktopNotificationSoundsSettings({ + active, + updateSection, + onSubmit, + onCancel, + saving, + error, + setParentState, + areAllSectionsInactive, + desktopSound, + desktopNotificationSound, + isCallsRingingEnabled, + callsDesktopSound, + callsNotificationSound, +}: Props) { + const intl = useIntl(); + + const editButtonRef = useRef(null); + const previousActiveRef = useRef(active); + + // Focus back on the edit button, after this section was closed after it was opened + useEffect(() => { + if (previousActiveRef.current && !active && areAllSectionsInactive) { + editButtonRef.current?.focus(); + } + + previousActiveRef.current = active; + }, [active, areAllSectionsInactive]); + + const handleChangeForMessageNotificationSoundCheckbox = useCallback((event: ChangeEvent) => { + const value = event.target.checked ? 'true' : 'false'; + setParentState('desktopSound', value); + + if (value === 'false') { + stopTryNotificationRing(); + } + }, [setParentState]); + + const handleChangeForIncomginCallSoundCheckbox = useCallback((event: ChangeEvent) => { + const value = event.target.checked ? 'true' : 'false'; + setParentState('callsDesktopSound', value); + + if (value === 'false') { + stopTryNotificationRing(); + } + }, [setParentState]); + + const handleChangeForMessageNotificationSoundSelect = useCallback((selectedOption: ValueType) => { + stopTryNotificationRing(); + + if (selectedOption && 'value' in selectedOption) { + setParentState('desktopNotificationSound', selectedOption.value); + tryNotificationSound(selectedOption.value); + } + }, [setParentState]); + + const handleChangeForIncomingCallSoundSelect = useCallback((selectedOption: ValueType) => { + stopTryNotificationRing(); + + if (selectedOption && 'value' in selectedOption) { + setParentState('callsNotificationSound', selectedOption.value); + tryNotificationRing(selectedOption.value); + } + }, [setParentState]); + + const maximizedSettingInputs = useMemo(() => { + const maximizedSettingInputs = []; + + const isMessageNotificationSoundChecked = desktopSound === 'true'; + const messageSoundSection = ( + +
+ + +
+
+ ); + maximizedSettingInputs.push(messageSoundSection); + + if (isCallsRingingEnabled) { + const isIncomingCallSoundChecked = callsDesktopSound === 'true'; + const callSoundSection = ( + +
+
+ + +
+
+ ); + maximizedSettingInputs.push(callSoundSection); + } + return maximizedSettingInputs; + }, + [ + desktopSound, + handleChangeForMessageNotificationSoundCheckbox, + handleChangeForMessageNotificationSoundSelect, + desktopNotificationSound, + isCallsRingingEnabled, + callsDesktopSound, + handleChangeForIncomginCallSoundCheckbox, + callsNotificationSound, + handleChangeForIncomingCallSoundSelect, + ]); + + function handleChangeForMaxSection(section: string) { + stopTryNotificationRing(); + updateSection(section); + } + + function handleChangeForMinSection(section: string) { + stopTryNotificationRing(); + updateSection(section); + onCancel(); + } + + function handleSubmit() { + stopTryNotificationRing(); + onSubmit(); + } + + if (active) { + return ( + + } + inputs={maximizedSettingInputs} + submit={handleSubmit} + saving={saving} + serverError={error} + updateSection={handleChangeForMaxSection} + /> + ); + } + + return ( + + } + describe={getCollapsedText(isCallsRingingEnabled, desktopSound, desktopNotificationSound, callsDesktopSound, callsNotificationSound)} + section={UserSettingsNotificationSections.DESKTOP_NOTIFICATION_SOUND} + updateSection={handleChangeForMinSection} + /> + ); +} + +function NoIndicatorSeparatorComponent() { + return null; +} + +const notificationSoundKeys = Array.from(notificationSounds.keys()); + +const optionsOfMessageNotificationSoundsSelect: SelectOption[] = notificationSoundKeys.map((soundName) => { + if (soundName === 'Bing') { + return { + value: soundName, + label: ( + + ), + }; + } else if (soundName === 'Crackle') { + return { + value: soundName, + label: ( + + ), + }; + } else if (soundName === 'Down') { + return { + value: soundName, + label: ( + + ), + }; + } else if (soundName === 'Hello') { + return { + value: soundName, + label: ( + + ), + }; + } else if (soundName === 'Ripple') { + return { + value: soundName, + label: ( + + ), + }; + } else if (soundName === 'Upstairs') { + return { + value: soundName, + label: ( + + ), + }; + } + return { + value: '', + label: '', + }; +}); + +function getValueOfMessageNotificationSoundsSelect(soundName?: string) { + const soundOption = optionsOfMessageNotificationSoundsSelect.find((option) => option.value === soundName); + + if (!soundOption) { + return undefined; + } + + return soundOption; +} + +const callNotificationSoundKeys = Array.from(callsNotificationSounds.keys()); + +const optionsOfIncomingCallSoundsSelect: SelectOption[] = callNotificationSoundKeys.map((soundName) => { + if (soundName === 'Dynamic') { + return { + value: soundName, + label: ( + + ), + }; + } else if (soundName === 'Calm') { + return { + value: soundName, + label: ( + + ), + }; + } else if (soundName === 'Urgent') { + return { + value: soundName, + label: ( + + ), + }; + } else if (soundName === 'Cheerful') { + return { + value: soundName, + label: ( + + ), + }; + } + return { + value: '', + label: '', + }; +}); + +function getValueOfIncomingCallSoundsSelect(soundName?: string) { + const soundOption = optionsOfIncomingCallSoundsSelect.find((option) => option.value === soundName); + + if (!soundOption) { + return undefined; + } + + return soundOption; +} + +function getCollapsedText( + isCallsRingingEnabled: UserSettingsNotificationsProps['isCallsRingingEnabled'], + desktopSound: UserNotifyProps['desktop_sound'], + desktopNotificationSound: UserNotifyProps['desktop_notification_sound'], + callsDesktopSound: UserNotifyProps['calls_desktop_sound'], + callsNotificationSound: UserNotifyProps['calls_notification_sound'], +) { + const desktopNotificationSoundIsSelected = notificationSoundKeys.includes(desktopNotificationSound as string); + const callNotificationSoundIsSelected = callNotificationSoundKeys.includes(callsNotificationSound as string); + + let hasCallsSound: boolean | null = null; + if (isCallsRingingEnabled && callNotificationSoundIsSelected) { + if (callsDesktopSound === 'true') { + hasCallsSound = true; + } else { + hasCallsSound = false; + } + } + + let hasDesktopSound: boolean | null = null; + if (desktopNotificationSoundIsSelected) { + if (desktopSound === 'true') { + hasDesktopSound = true; + } else { + hasDesktopSound = false; + } + } + + if (hasDesktopSound !== null && hasCallsSound !== null) { + if (hasDesktopSound && hasCallsSound) { + return ( + + ); + } else if (!hasDesktopSound && hasCallsSound) { + return ( + + ); + } else if (hasDesktopSound && !hasCallsSound) { + return ( + + ); + } + + return ( + + ); + } else if (hasDesktopSound !== null && hasCallsSound === null) { + if (hasDesktopSound) { + return ( + + ); + } + + return ( + + ); + } + + return ( + + ); +} + +export default memo(DesktopNotificationSoundsSettings); diff --git a/webapp/channels/src/components/user_settings/notifications/email_notification_setting/__snapshots__/email_notification_setting.test.tsx.snap b/webapp/channels/src/components/user_settings/notifications/email_notification_setting/__snapshots__/email_notification_setting.test.tsx.snap index 938f39813f..8324ed9905 100644 --- a/webapp/channels/src/components/user_settings/notifications/email_notification_setting/__snapshots__/email_notification_setting.test.tsx.snap +++ b/webapp/channels/src/components/user_settings/notifications/email_notification_setting/__snapshots__/email_notification_setting.test.tsx.snap @@ -95,7 +95,7 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should submit={[Function]} title={ } @@ -109,16 +109,16 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should id="settingTitle" > - Email Notifications + Email notifications
} @@ -308,7 +308,7 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should section="email" title={ } @@ -327,7 +327,7 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should section="email" title={ } @@ -479,7 +479,7 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should submit={[Function]} title={ } @@ -493,16 +493,16 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should id="settingTitle" > - Email Notifications + Email notifications
} @@ -824,7 +824,7 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should submit={[Function]} title={ } @@ -898,16 +898,8 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
- - -
-
-
-
-
, @@ -942,7 +925,7 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should submit={[Function]} title={ } @@ -1022,7 +1005,7 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should submit={[Function]} title={ } diff --git a/webapp/channels/src/components/user_settings/notifications/email_notification_setting/email_notification_setting.tsx b/webapp/channels/src/components/user_settings/notifications/email_notification_setting/email_notification_setting.tsx index eef6aa7dec..e16895e992 100644 --- a/webapp/channels/src/components/user_settings/notifications/email_notification_setting/email_notification_setting.tsx +++ b/webapp/channels/src/components/user_settings/notifications/email_notification_setting/email_notification_setting.tsx @@ -237,8 +237,8 @@ export default class EmailNotificationSetting extends React.PureComponent } describe={description} @@ -254,8 +254,8 @@ export default class EmailNotificationSetting extends React.PureComponent } inputs={[ @@ -334,13 +334,7 @@ export default class EmailNotificationSetting extends React.PureComponent
- - - -
+
-
-
-
-
@@ -371,8 +358,8 @@ export default class EmailNotificationSetting extends React.PureComponent } inputs={[ diff --git a/webapp/channels/src/components/user_settings/notifications/manage_auto_responder/__snapshots__/manage_auto_responder.test.tsx.snap b/webapp/channels/src/components/user_settings/notifications/manage_auto_responder/__snapshots__/manage_auto_responder.test.tsx.snap index 85742efd15..a2332b2078 100644 --- a/webapp/channels/src/components/user_settings/notifications/manage_auto_responder/__snapshots__/manage_auto_responder.test.tsx.snap +++ b/webapp/channels/src/components/user_settings/notifications/manage_auto_responder/__snapshots__/manage_auto_responder.test.tsx.snap @@ -48,12 +48,11 @@ exports[`components/user_settings/notifications/ManageAutoResponder should match submit={[MockFunction]} title={ } updateSection={[MockFunction]} - width="medium" >
- Automatic Direct Message Replies + Automatic direct message replies
} updateSection={[MockFunction]} - width="medium" >
- Automatic Direct Message Replies + Automatic direct message replies
{ title={ } - width='medium' shiftEnter={true} submit={this.props.submit} saving={this.props.saving} 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 7a20d6f733..6c8d74ef67 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 @@ -5,9 +5,10 @@ import React from 'react'; import {type IntlShape} from 'react-intl'; import {renderWithContext, screen} from 'tests/react_testing_utils'; +import {NotificationLevels} from 'utils/constants'; import {TestHelper} from 'utils/test_helper'; -import UserSettingsNotifications from './user_settings_notifications'; +import UserSettingsNotifications, {areDesktopAndMobileSettingsDifferent} from './user_settings_notifications'; describe('components/user_settings/display/UserSettingsDisplay', () => { const defaultProps = { @@ -20,7 +21,7 @@ describe('components/user_settings/display/UserSettingsDisplay', () => { isCollapsedThreadsEnabled: true, sendPushNotifications: false, enableAutoResponder: false, - isCallsRingingEnabled: true, + isCallsRingingEnabled: false, intl: {} as IntlShape, isEnterpriseOrCloudOrSKUStarterFree: false, isEnterpriseReady: true, @@ -62,3 +63,27 @@ describe('components/user_settings/display/UserSettingsDisplay', () => { expect(screen.getByText('Reply notifications')).toBeInTheDocument(); }); }); + +describe('areDesktopAndMobileSettingsDifferent', () => { + test('should return true when desktop and push notification levels are different', () => { + expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.MENTION, NotificationLevels.ALL, NotificationLevels.NONE, true)).toBe(true); + expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.NONE, NotificationLevels.ALL, NotificationLevels.MENTION, true)).toBe(true); + expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.NONE, NotificationLevels.MENTION, NotificationLevels.NONE, true)).toBe(true); + expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.MENTION, NotificationLevels.NONE, NotificationLevels.NONE, true)).toBe(true); + expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.NONE, NotificationLevels.NONE, NotificationLevels.NONE, true)).toBe(true); + expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.MENTION, NotificationLevels.ALL, NotificationLevels.MENTION, true)).toBe(true); + expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.NONE, NotificationLevels.ALL, NotificationLevels.NONE, true)).toBe(true); + }); + + test('should return false when desktop and push notification levels are the same', () => { + expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.ALL, NotificationLevels.ALL, NotificationLevels.ALL, true)).toBe(false); + expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.MENTION, NotificationLevels.MENTION, NotificationLevels.MENTION, NotificationLevels.MENTION, false)).toBe(false); + }); + + test('should return true any of desktop or push settings are undefined', () => { + expect(areDesktopAndMobileSettingsDifferent(undefined as any, NotificationLevels.ALL, NotificationLevels.ALL, NotificationLevels.ALL, true)).toBe(true); + expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, undefined as any, NotificationLevels.ALL, NotificationLevels.ALL, true)).toBe(true); + expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.ALL, undefined as any, NotificationLevels.ALL, true)).toBe(true); + expect(areDesktopAndMobileSettingsDifferent(NotificationLevels.ALL, NotificationLevels.ALL, NotificationLevels.ALL, undefined as any, true)).toBe(true); + }); +}); 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 3c281ce205..5806da80a8 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 @@ -11,21 +11,19 @@ import type {Styles as ReactSelectStyles, ValueType} from 'react-select'; import CreatableReactSelect from 'react-select/creatable'; import {LightbulbOutlineIcon} from '@mattermost/compass-icons/components'; -import type {ServerError} from '@mattermost/types/errors'; import type {UserNotifyProps, UserProfile} from '@mattermost/types/users'; -import type {ActionResult} from 'mattermost-redux/types/actions'; - import ExternalLink from 'components/external_link'; import SettingItem from 'components/setting_item'; import SettingItemMax from 'components/setting_item_max'; import RestrictedIndicator from 'components/widgets/menu/menu_items/restricted_indicator'; -import Constants, {NotificationLevels, MattermostFeatures, LicenseSkus} from 'utils/constants'; +import Constants, {NotificationLevels, MattermostFeatures, LicenseSkus, UserSettingsNotificationSections} from 'utils/constants'; import {stopTryNotificationRing} from 'utils/notification_sounds'; import {a11yFocus} from 'utils/utils'; -import DesktopNotificationSettings from './desktop_notification_setting/desktop_notification_settings'; +import DesktopAndMobileNotificationSettings from './desktop_and_mobile_notification_setting'; +import DesktopNotificationSoundsSettings from './desktop_notification_sounds_setting'; import EmailNotificationSetting from './email_notification_setting'; import ManageAutoResponder from './manage_auto_responder/manage_auto_responder'; @@ -50,7 +48,7 @@ type OwnProps = { collapseModal: () => void; } -type Props = PropsFromRedux & OwnProps & WrappedComponentProps; +export type Props = PropsFromRedux & OwnProps & WrappedComponentProps; type State = { enableEmail: UserNotifyProps['email']; @@ -77,6 +75,7 @@ type State = { notifyCommentsLevel: UserNotifyProps['comments']; isSaving: boolean; serverError: string; + desktopAndMobileSettingsDifferent: boolean; }; function getDefaultStateFromProps(props: Props): State { @@ -97,6 +96,7 @@ function getDefaultStateFromProps(props: Props): State { id: 'user.settings.notifications.autoResponderDefault', defaultMessage: 'Hello, I am out of office and unable to respond to messages.', }); + let desktopAndMobileSettingsDifferent = true; if (props.user.notify_props) { if (props.user.notify_props.desktop) { @@ -143,6 +143,10 @@ function getDefaultStateFromProps(props: Props): State { if (props.user.notify_props.auto_responder_message) { autoResponderMessage = props.user.notify_props.auto_responder_message; } + + if (props.user.notify_props.desktop && props.user.notify_props.push) { + desktopAndMobileSettingsDifferent = areDesktopAndMobileSettingsDifferent(props.user.notify_props.desktop, props.user.notify_props.push, props.user.notify_props?.desktop_threads, props.user.notify_props?.push_threads, props.isCollapsedThreadsEnabled); + } } let usernameKey = false; @@ -211,6 +215,7 @@ function getDefaultStateFromProps(props: Props): State { notifyCommentsLevel: comments, isSaving: false, serverError: '', + desktopAndMobileSettingsDifferent, }; } @@ -235,8 +240,6 @@ class NotificationsTab extends React.PureComponent { data.desktop = this.state.desktopActivity; data.desktop_threads = this.state.desktopThreads; data.email_threads = this.state.emailThreads; - data.push_threads = this.state.pushThreads; - data.push = this.state.pushActivity; data.push_status = this.state.pushStatus; data.comments = this.state.notifyCommentsLevel; data.auto_responder_active = this.state.autoResponderActive ? 'true' : 'false'; @@ -244,6 +247,14 @@ class NotificationsTab extends React.PureComponent { data.first_name = this.state.firstNameKey ? 'true' : 'false'; data.channel = this.state.channelKey ? 'true' : 'false'; + if (this.state.desktopAndMobileSettingsDifferent) { + data.push = this.state.pushActivity; + data.push_threads = this.state.pushThreads; + } else { + data.push = this.state.desktopActivity; + data.push_threads = this.state.desktopThreads; + } + if (!data.auto_responder_message || data.auto_responder_message === '') { data.auto_responder_message = this.props.intl.formatMessage({ id: 'user.settings.notifications.autoResponderDefault', @@ -273,7 +284,7 @@ class NotificationsTab extends React.PureComponent { this.setState({isSaving: true}); stopTryNotificationRing(); - const {data: updatedUser, error} = await this.props.updateMe({notify_props: data}) as ActionResult, ServerError>; // Fix in MM-46907 + const {data: updatedUser, error} = await this.props.updateMe({notify_props: data}); if (updatedUser) { this.handleUpdateSection(''); this.setState(getDefaultStateFromProps(this.props)); @@ -305,26 +316,11 @@ class NotificationsTab extends React.PureComponent { this.setState((prevState) => ({...prevState, ...data})); }; - handleNotifyPushThread = (e: ChangeEvent): void => { - const pushThreads = e.target.checked ? NotificationLevels.ALL : NotificationLevels.MENTION; - this.setState({pushThreads}); - }; - handleNotifyCommentsRadio = (notifyCommentsLevel: UserNotifyProps['comments'], e?: React.ChangeEvent): void => { this.setState({notifyCommentsLevel}); a11yFocus(e?.currentTarget as HTMLElement); }; - handlePushRadio = (pushActivity: UserNotifyProps['push'], e?: React.ChangeEvent): void => { - this.setState({pushActivity}); - a11yFocus(e?.currentTarget as HTMLElement); - }; - - handlePushStatusRadio = (pushStatus: UserNotifyProps['push_status'], e?: React.ChangeEvent): void => { - this.setState({pushStatus}); - a11yFocus(e?.currentTarget as HTMLElement); - }; - handleEmailRadio = (enableEmail: UserNotifyProps['email']): void => { this.setState({enableEmail}); }; @@ -472,311 +468,10 @@ class NotificationsTab extends React.PureComponent { this.props.closeModal(); }; - createPushNotificationSection = () => { - const active = this.props.activeSection === 'push'; - const inputs = []; - let submit = null; - let max = null; - - if (active) { - if (this.props.sendPushNotifications) { - const pushActivityRadio = [false, false, false]; - if (this.state.pushActivity === NotificationLevels.ALL) { - pushActivityRadio[0] = true; - } else if (this.state.pushActivity === NotificationLevels.NONE) { - pushActivityRadio[2] = true; - } else { - pushActivityRadio[1] = true; - } - - const pushStatusRadio = [false, false, false]; - if (this.state.pushStatus === Constants.UserStatuses.ONLINE) { - pushStatusRadio[0] = true; - } else if (this.state.pushStatus === Constants.UserStatuses.AWAY) { - pushStatusRadio[1] = true; - } else { - pushStatusRadio[2] = true; - } - - let pushThreadsNotificationSelection = null; - if (this.props.isCollapsedThreadsEnabled && this.state.pushActivity === NotificationLevels.MENTION) { - pushThreadsNotificationSelection = ( - -
-
- - - -
- -
-
-
- -
-
-
- ); - } - let pushStatusSettings; - if (this.state.pushActivity !== NotificationLevels.NONE) { - pushStatusSettings = ( - -
-
- - - -
- -
-
- -
-
- -
-
- - - -
-
-
- ); - } - - inputs.push( -
-
- - - -
- -
-
- -
-
- -
-
- -
-
-
, - pushStatusSettings, - pushThreadsNotificationSelection, - ); - - submit = this.handleSubmit; - } else { - inputs.push( -
- -
, - ); - } - max = ( - - ); - } - - let describe: JSX.Element; - if (this.state.pushActivity === NotificationLevels.ALL) { - if (this.state.pushStatus === Constants.UserStatuses.AWAY) { - describe = ( - - ); - } else if (this.state.pushStatus === Constants.UserStatuses.OFFLINE) { - describe = ( - - ); - } else { - describe = ( - - ); - } - } else if (this.state.pushActivity === NotificationLevels.NONE) { - describe = ( - - ); - } else if (this.props.sendPushNotifications) { - if (this.state.pushStatus === Constants.UserStatuses.AWAY) { //eslint-disable-line no-lonely-if - describe = ( - - ); - } else if (this.state.pushStatus === Constants.UserStatuses.OFFLINE) { - describe = ( - - ); - } else { - describe = ( - - ); - } - } else { - describe = ( - - ); - } - - return ( - - ); - }; - createKeywordsWithNotificationSection = () => { const serverError = this.state.serverError; const user = this.props.user; - const isSectionExpanded = this.props.activeSection === 'keysWithNotification'; + const isSectionExpanded = this.props.activeSection === UserSettingsNotificationSections.KEYWORDS_MENTIONS; let expandedSection = null; if (isSectionExpanded) { @@ -901,7 +596,7 @@ class NotificationsTab extends React.PureComponent { expandedSection = ( { return ( { }; createKeywordsWithHighlightSection = () => { - const isSectionExpanded = this.props.activeSection === 'keysWithHighlight'; + const isSectionExpanded = this.props.activeSection === UserSettingsNotificationSections.KEYWORDS_HIGHLIGHT; let expandedSection = null; if (isSectionExpanded) { @@ -990,7 +685,7 @@ class NotificationsTab extends React.PureComponent { expandedSection = ( { return ( { createCommentsSection = () => { const serverError = this.state.serverError; - const active = this.props.activeSection === 'comments'; let max = null; - if (active) { + if (this.props.activeSection === UserSettingsNotificationSections.REPLY_NOTIFCATIONS) { const commentsActive = [false, false, false]; if (this.state.notifyCommentsLevel === 'never') { commentsActive[2] = true; @@ -1097,7 +791,7 @@ class NotificationsTab extends React.PureComponent {
@@ -1161,7 +855,7 @@ class NotificationsTab extends React.PureComponent { max = ( { return ( { return ( } describe={describe} - section={'auto-responder'} + section={UserSettingsNotificationSections.AUTO_RESPONDER} updateSection={this.handleUpdateSection} max={(
@@ -1254,12 +948,13 @@ class NotificationsTab extends React.PureComponent { }; render() { - const pushNotificationSection = this.createPushNotificationSection(); const keywordsWithNotificationSection = this.createKeywordsWithNotificationSection(); const keywordsWithHighlightSection = this.createKeywordsWithHighlightSection(); const commentsSection = this.createCommentsSection(); const autoResponderSection = this.createAutoResponderSection(); + const areAllSectionsInactive = this.props.activeSection === ''; + return (
{ text={ } /> @@ -1302,42 +997,56 @@ class NotificationsTab extends React.PureComponent { } />
- +
+
- {pushNotificationSection} -
{keywordsWithNotificationSection} {(!this.props.isEnterpriseOrCloudOrSKUStarterFree && this.props.isEnterpriseReady) && ( <> @@ -1414,4 +1123,39 @@ const customKeywordsSelectorStyles: ReactSelectStyles = { })), }; +const validNotificationLevels = Object.values(NotificationLevels); + +export function areDesktopAndMobileSettingsDifferent( + desktopActivity: UserNotifyProps['desktop'], + pushActivity: UserNotifyProps['push'], + desktopThreads?: UserNotifyProps['desktop_threads'], + pushThreads?: UserNotifyProps['push_threads'], + isCollapsedThreadsEnabled?: boolean, +): boolean { + if (!desktopActivity || !pushActivity || !desktopThreads || !pushThreads) { + return true; + } + + if ( + !validNotificationLevels.includes(desktopActivity) || + !validNotificationLevels.includes(pushActivity) || + !validNotificationLevels.includes(desktopThreads) || + !validNotificationLevels.includes(pushThreads) + ) { + return true; + } + + if (desktopActivity === pushActivity) { + if (isCollapsedThreadsEnabled) { + if (desktopThreads === pushThreads) { + return false; + } + return true; + } + return false; + } + + return true; +} + export default injectIntl(NotificationsTab); diff --git a/webapp/channels/src/components/user_settings/security/mfa_section/__snapshots__/mfa_section.test.tsx.snap b/webapp/channels/src/components/user_settings/security/mfa_section/__snapshots__/mfa_section.test.tsx.snap index 59aae139f5..23e737c487 100644 --- a/webapp/channels/src/components/user_settings/security/mfa_section/__snapshots__/mfa_section.test.tsx.snap +++ b/webapp/channels/src/components/user_settings/security/mfa_section/__snapshots__/mfa_section.test.tsx.snap @@ -77,7 +77,6 @@ exports[`MfaSection rendering when section is expanded and MFA is active and enf /> } updateSection={[MockFunction]} - width="medium" /> `; @@ -118,7 +117,6 @@ exports[`MfaSection rendering when section is expanded and MFA is active but not /> } updateSection={[MockFunction]} - width="medium" /> `; @@ -159,7 +157,6 @@ exports[`MfaSection rendering when section is expanded and MFA is not active 1`] /> } updateSection={[MockFunction]} - width="medium" /> `; @@ -200,6 +197,5 @@ exports[`MfaSection rendering when section is expanded with a server error 1`] = /> } updateSection={[MockFunction]} - width="medium" /> `; diff --git a/webapp/channels/src/components/user_settings/security/mfa_section/mfa_section.tsx b/webapp/channels/src/components/user_settings/security/mfa_section/mfa_section.tsx index ce1cfc2a09..43cdab8049 100644 --- a/webapp/channels/src/components/user_settings/security/mfa_section/mfa_section.tsx +++ b/webapp/channels/src/components/user_settings/security/mfa_section/mfa_section.tsx @@ -219,7 +219,6 @@ export default class MfaSection extends React.PureComponent { extraInfo={this.renderHelpText()} serverError={this.state.serverError} updateSection={this.props.updateSection} - width='medium' /> ); } diff --git a/webapp/channels/src/components/user_settings/security/user_access_token_section/user_access_token_section.tsx b/webapp/channels/src/components/user_settings/security/user_access_token_section/user_access_token_section.tsx index 7d8a9b33c6..b03a4fa0a1 100644 --- a/webapp/channels/src/components/user_settings/security/user_access_token_section/user_access_token_section.tsx +++ b/webapp/channels/src/components/user_settings/security/user_access_token_section/user_access_token_section.tsx @@ -627,7 +627,7 @@ export default class UserAccessTokenSection extends React.PureComponent { inputs={inputs} serverError={this.state.serverError} updateSection={this.handleUpdateSection} - width='full' + isFullWidth={true} cancelButtonText={ Learn more about notifications", - "user.settings.notifications.never": "Never", - "user.settings.notifications.off": "Off", - "user.settings.notifications.on": "On", - "user.settings.notifications.onlyMentions": "Only for mentions, direct messages, and group messages", - "user.settings.notifications.push": "Mobile Push Notifications", - "user.settings.notifications.push_notification.status": "Trigger push notifications when", - "user.settings.notifications.push_threads": "When enabled, any reply to a thread you're following will send a mobile push notification.", - "user.settings.notifications.push_threads.allActivity": "Notify me about threads I'm following", "user.settings.notifications.sensitiveCustomWords": "Other non case-sensitive words, press Tab or use commas to separate keywords:", "user.settings.notifications.sensitiveName": "Your case-sensitive first name \"{first_name}\"", "user.settings.notifications.sensitiveUsername": "Your non case-sensitive username \"{username}\"", - "user.settings.notifications.soundConfig": "Please configure notification sounds in your browser settings", - "user.settings.notifications.sounds_info": "Notification sounds are available on Firefox, Edge, Safari, Chrome and Mattermost Desktop Apps.", - "user.settings.notifications.threads": "When enabled, any reply to a thread you're following will send a desktop notification.", - "user.settings.notifications.threads.allActivity": "Notify me about threads I'm following", - "user.settings.notifications.threads.desktop": "Thread reply notifications", - "user.settings.notifications.threads.push": "Thread reply notifications", - "user.settings.notifications.title": "Notification Settings", + "user.settings.notifications.title": "Notification settings", "user.settings.plugins.title": "{pluginName} Settings", "user.settings.profile.icon": "Profile Settings Icon", - "user.settings.push_notification.allActivity": "For all activity", - "user.settings.push_notification.allActivityAway": "For all activity when away or offline", - "user.settings.push_notification.allActivityOffline": "For all activity when offline", - "user.settings.push_notification.allActivityOnline": "For all activity when online, away or offline", - "user.settings.push_notification.away": "Away or offline", - "user.settings.push_notification.disabled": "Push notifications are not enabled", - "user.settings.push_notification.disabled_long": "Push notifications have not been enabled by your System Administrator.", - "user.settings.push_notification.info": "Notification alerts are pushed to your mobile device when there is activity in Mattermost.", - "user.settings.push_notification.offline": "Offline", - "user.settings.push_notification.online": "Online, away or offline", - "user.settings.push_notification.onlyMentions": "For mentions and direct messages", - "user.settings.push_notification.onlyMentionsAway": "For mentions and direct messages when away or offline", - "user.settings.push_notification.onlyMentionsOffline": "For mentions and direct messages when offline", - "user.settings.push_notification.onlyMentionsOnline": "For mentions and direct messages when online, away or offline", - "user.settings.push_notification.send": "Send mobile push notifications", - "user.settings.push_notification.status_info": "Notification alerts are only pushed to your mobile device when your availability matches the selection above.", "user.settings.security.active": "Active", "user.settings.security.close": "Close", "user.settings.security.currentPassword": "Current Password", diff --git a/webapp/channels/src/sass/components/_forms.scss b/webapp/channels/src/sass/components/_forms.scss index e3315aa97f..70227e3967 100644 --- a/webapp/channels/src/sass/components/_forms.scss +++ b/webapp/channels/src/sass/components/_forms.scss @@ -204,7 +204,3 @@ input[type="radio"]:focus, input[type="checkbox"]:focus { outline: none; } - -.notification-sound-dropdown { - padding-top: 5px; -} diff --git a/webapp/channels/src/sass/routes/_settings.scss b/webapp/channels/src/sass/routes/_settings.scss index 3b8e09bab3..7ba4f9783e 100644 --- a/webapp/channels/src/sass/routes/_settings.scss +++ b/webapp/channels/src/sass/routes/_settings.scss @@ -79,8 +79,6 @@ } label { - font-weight: 600; - &.text-left { text-align: left; } @@ -221,17 +219,56 @@ .section-max { @include pie-clearfix; - padding: 1em 0 1.3em; + padding: 12px; margin-bottom: 0; background: rgba(var(--center-channel-color-rgb), 0.04); .section-title { - padding-left: 14px; - margin: 0 0 5px; + padding: 0; + margin: 0; font-size: 14px; - font-weight: 600; + font-weight: 400; line-height: 20px; } + + .sectionContent { + padding: 20px 0 0 0; + + .checkbox.single-checkbox { + min-height: unset; + padding: 0; + } + + .inlineCheckboxSelect { + display: flex; + min-height: unset; + align-items: center; + justify-content: space-between; + padding: 0; + + label { + margin-inline-end: 24px; + white-space: nowrap; + } + + .inlineSelect { + width: 40%; + } + } + + .singleSelectLabel { + margin-bottom: 12px; + font-size: 14px; + font-weight: 600; + line-height: 20px; + } + + hr { + padding: 0; + margin-block-end: 24px; + margin-block-start: 24px; + } + } } .timezone-container { diff --git a/webapp/channels/src/utils/constants.tsx b/webapp/channels/src/utils/constants.tsx index f2b8957d1a..125ba9595f 100644 --- a/webapp/channels/src/utils/constants.tsx +++ b/webapp/channels/src/utils/constants.tsx @@ -966,13 +966,14 @@ export const ChannelAutoFollowThreads = { OFF: 'off', } as const; -export const NotificationSections = { - IGNORE_CHANNEL_MENTIONS: 'ignoreChannelMentions', - CHANNEL_AUTO_FOLLOW_THREADS: 'channelAutoFollowThreads', - MARK_UNREAD: 'markUnread', - DESKTOP: 'desktop', - PUSH: 'push', - NONE: '', +export const UserSettingsNotificationSections = { + DESKTOP_AND_MOBILE: 'desktopAndMobile', + DESKTOP_NOTIFICATION_SOUND: 'desktopNotificationSound', + EMAIL: 'email', + KEYWORDS_MENTIONS: 'keywordsAndMentions', + KEYWORDS_HIGHLIGHT: 'keywordsAndHighlight', + REPLY_NOTIFCATIONS: 'replyNotifications', + AUTO_RESPONDER: 'autoResponder', }; export const AdvancedSections = {