diff --git a/e2e-tests/cypress/tests/integration/channels/collapsed_reply_threads/channel_notifications_spec.js b/e2e-tests/cypress/tests/integration/channels/collapsed_reply_threads/channel_notifications_spec.js
index 6f1aa45dca..4263a5d522 100644
--- a/e2e-tests/cypress/tests/integration/channels/collapsed_reply_threads/channel_notifications_spec.js
+++ b/e2e-tests/cypress/tests/integration/channels/collapsed_reply_threads/channel_notifications_spec.js
@@ -144,8 +144,8 @@ describe('CRT Desktop notifications', () => {
cy.visit(testChannelUrl);
cy.uiOpenChannelMenu('Notification Preferences');
- cy.get('.channel-notifications-settings-modal__body').scrollTo('center').get('#desktopNotification-mention').should('be.visible').click().then(() => {
- cy.get('[data-testid="desktopReplyThreads"]').should('be.visible').click();
+ cy.get('#desktopNotification-mention').scrollIntoView().should('be.visible').click().then(() => {
+ cy.get('[data-testid="desktopReplyThreads"]').scrollIntoView().should('be.visible').click();
});
cy.get('.channel-notifications-settings-modal__body').scrollTo('center').get('[data-testid="desktopReplyThreads"]').should('be.visible').click();
cy.get('.channel-notifications-settings-modal__body').get('[data-testid="sameMobileSettingsDesktop"]').scrollIntoView().click().should('be.checked').then(() => {
@@ -174,7 +174,7 @@ describe('CRT Desktop notifications', () => {
// Setup notification spy
spyNotificationAs('notifySpy', 'granted');
cy.uiOpenChannelMenu('Notification Preferences');
- cy.get('.channel-notifications-settings-modal__body').scrollTo('center').get('#desktopNotification-mention').should('be.visible').click();
+ cy.get('#desktopNotification-mention').scrollIntoView().should('be.visible').click();
// # Save the changes
cy.findByText('Save').should('be.visible').click();
diff --git a/webapp/channels/src/components/channel_notifications_modal/__snapshots__/channel_notifications_modal.test.tsx.snap b/webapp/channels/src/components/channel_notifications_modal/__snapshots__/channel_notifications_modal.test.tsx.snap
index 0b27528c39..c4148ed0e4 100644
--- a/webapp/channels/src/components/channel_notifications_modal/__snapshots__/channel_notifications_modal.test.tsx.snap
+++ b/webapp/channels/src/components/channel_notifications_modal/__snapshots__/channel_notifications_modal.test.tsx.snap
@@ -220,6 +220,87 @@ Object {
+
+
+ Sounds
+
+
+
+
+
+
+
+
+ Sounds
+
+
+
+
+
+
+
+
+ Sounds
+
+
+
+
+
+
+
+
+ Sounds
+
+
+
+
+
+
{
+ renderWithContext();
+
+ // Since the default value is on, we will uncheck the checkbox
+ fireEvent.click(screen.getByTestId('desktopNotificationSoundsCheckbox'));
+ expect(screen.getByTestId('desktopNotificationSoundsCheckbox')).not.toBeChecked();
+
+ fireEvent.click(screen.getByRole('button', {name: /Save/i}));
+ await waitFor(() => {
+ expect(baseProps.actions.updateChannelNotifyProps).toHaveBeenCalledWith(
+ 'current_user_id',
+ 'channel_id',
+ {
+ desktop: 'all',
+ ignore_channel_mentions: 'off',
+ mark_unread: 'all',
+ push: 'all',
+ desktop_sound: 'off',
+ desktop_notification_sound: 'Bing',
+ },
+ );
+ });
+ });
+
+ test('should default to user desktop notification sound if reset to default is clicked', async () => {
+ renderWithContext();
+
+ // Since the default value is on, we will uncheck the checkbox
+ fireEvent.click(screen.getByTestId('desktopNotificationSoundsCheckbox'));
+ expect(screen.getByTestId('desktopNotificationSoundsCheckbox')).not.toBeChecked();
+
+ // Reset to default button is clicked
+ fireEvent.click(screen.getByTestId('resetToDefaultButton-desktop'));
+
+ // Verify that the checkbox is checked to default to user desktop notification sound
+ expect(screen.getByTestId('desktopNotificationSoundsCheckbox')).toBeChecked();
+ });
+
test('should save the options exactly same as Desktop for mobile if use same as desktop checkbox is checked', async () => {
const wrapper = renderWithContext(
,
@@ -170,6 +218,8 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
ignore_channel_mentions: 'off',
mark_unread: 'all',
push: 'all',
+ desktop_sound: 'on',
+ desktop_notification_sound: 'Bing',
},
),
);
@@ -209,6 +259,8 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
ignore_channel_mentions: 'off',
mark_unread: 'all',
push: 'none',
+ desktop_sound: 'on',
+ desktop_notification_sound: 'Bing',
},
),
);
@@ -240,6 +292,8 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
push: 'all',
push_threads: 'default',
desktop_threads: 'all',
+ desktop_sound: 'on',
+ desktop_notification_sound: 'Bing',
},
),
);
diff --git a/webapp/channels/src/components/channel_notifications_modal/channel_notifications_modal.tsx b/webapp/channels/src/components/channel_notifications_modal/channel_notifications_modal.tsx
index 1c12e76c5c..cfc2cc93ba 100644
--- a/webapp/channels/src/components/channel_notifications_modal/channel_notifications_modal.tsx
+++ b/webapp/channels/src/components/channel_notifications_modal/channel_notifications_modal.tsx
@@ -4,6 +4,7 @@
import React, {useCallback, useState} from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage, useIntl} from 'react-intl';
+import type {ValueType} from 'react-select';
import {BellOffOutlineIcon, RefreshIcon} from '@mattermost/compass-icons/components';
import type {Channel, ChannelNotifyProps} from '@mattermost/types/channels';
@@ -11,14 +12,17 @@ import type {UserNotifyProps, UserProfile} from '@mattermost/types/users';
import AlertBanner from 'components/alert_banner';
import CheckboxSettingItem from 'components/widgets/modals/components/checkbox_setting_item';
+import CheckboxWithSelectSettingItem from 'components/widgets/modals/components/checkbox_with_select_item';
import ModalHeader from 'components/widgets/modals/components/modal_header';
import ModalSection from 'components/widgets/modals/components/modal_section';
import RadioSettingItem from 'components/widgets/modals/components/radio_setting_item';
+import type {Option} from 'components/widgets/modals/components/react_select_item';
-import {IgnoreChannelMentions, NotificationLevels} from 'utils/constants';
+import {IgnoreChannelMentions, NotificationLevels, DesktopSound} from 'utils/constants';
+import {getValueOfNotificationSoundsSelect, notificationSoundKeys, stopTryNotificationRing, tryNotificationSound} from 'utils/notification_sounds';
import type {ChannelMemberNotifyProps} from './utils';
-import utils from './utils';
+import utils, {convertDesktopSoundNotifyPropFromUserToDesktop} from './utils';
import type {PropsFromRedux} from './index';
@@ -63,9 +67,27 @@ function getStateFromNotifyProps(currentUserNotifyProps: UserNotifyProps, channe
const desktop = channelMemberNotifyProps?.desktop === NotificationLevels.DEFAULT ? currentUserNotifyProps.desktop : (channelMemberNotifyProps?.desktop || currentUserNotifyProps.desktop);
const push = channelMemberNotifyProps?.push === NotificationLevels.DEFAULT ? currentUserNotifyProps.desktop : (channelMemberNotifyProps?.push || currentUserNotifyProps.push);
+ let desktopSound;
+ if (channelMemberNotifyProps && channelMemberNotifyProps.desktop_sound) {
+ desktopSound = channelMemberNotifyProps.desktop_sound;
+ } else {
+ desktopSound = convertDesktopSoundNotifyPropFromUserToDesktop(currentUserNotifyProps.desktop_sound);
+ }
+
+ let desktopNotificationSound;
+ if (channelMemberNotifyProps && channelMemberNotifyProps.desktop_notification_sound) {
+ desktopNotificationSound = channelMemberNotifyProps.desktop_notification_sound;
+ } else if (currentUserNotifyProps && currentUserNotifyProps.desktop_notification_sound) {
+ desktopNotificationSound = currentUserNotifyProps.desktop_notification_sound;
+ } else {
+ desktopNotificationSound = notificationSoundKeys[0] as ChannelNotifyProps['desktop_notification_sound'];
+ }
+
return {
desktop,
desktop_threads: channelMemberNotifyProps?.desktop_threads || NotificationLevels.ALL,
+ desktop_sound: desktopSound,
+ desktop_notification_sound: desktopNotificationSound,
mark_unread: channelMemberNotifyProps?.mark_unread || NotificationLevels.ALL,
push,
push_threads: channelMemberNotifyProps?.push_threads || NotificationLevels.ALL,
@@ -77,6 +99,8 @@ function getStateFromNotifyProps(currentUserNotifyProps: UserNotifyProps, channe
type SettingsType = {
desktop: ChannelNotifyProps['desktop'];
desktop_threads: ChannelNotifyProps['desktop_threads'];
+ desktop_sound: ChannelNotifyProps['desktop_sound'];
+ desktop_notification_sound: ChannelNotifyProps['desktop_notification_sound'];
mark_unread: ChannelNotifyProps['mark_unread'];
push: ChannelNotifyProps['push'];
push_threads: ChannelNotifyProps['push_threads'];
@@ -104,7 +128,7 @@ export default function ChannelNotificationsModal(props: Props) {
setSettings((prevSettings) => ({...prevSettings, push: prevSettings.desktop, push_threads: prevSettings.desktop_threads}));
}, []);
- const MuteOrIgnoreSectionContent = (
+ const muteOrIgnoreSectionContent = (
<>
);
- const DesktopNotificationsSectionContent = (
+ const handleChangeForMessageNotificationSoundSelect = (selectedOption: ValueType
@@ -259,146 +263,6 @@ 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'],
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 3488639681..fae5fddebe 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
@@ -20,7 +20,7 @@ import SettingItemMax from 'components/setting_item_max';
import RestrictedIndicator from 'components/widgets/menu/menu_items/restricted_indicator';
import Constants, {NotificationLevels, MattermostFeatures, LicenseSkus, UserSettingsNotificationSections} from 'utils/constants';
-import {stopTryNotificationRing} from 'utils/notification_sounds';
+import {notificationSoundKeys, stopTryNotificationRing} from 'utils/notification_sounds';
import {a11yFocus} from 'utils/utils';
import DesktopAndMobileNotificationSettings from './desktop_and_mobile_notification_setting';
@@ -88,7 +88,7 @@ function getDefaultStateFromProps(props: Props): State {
let emailThreads: UserNotifyProps['email_threads'] = NotificationLevels.ALL;
let sound: UserNotifyProps['desktop_sound'] = 'true';
let callsSound: UserNotifyProps['calls_desktop_sound'] = 'true';
- let desktopNotificationSound: UserNotifyProps['desktop_notification_sound'] = 'Bing';
+ let desktopNotificationSound: UserNotifyProps['desktop_notification_sound'] = notificationSoundKeys[0] as UserNotifyProps['desktop_notification_sound'];
let callsNotificationSound: UserNotifyProps['calls_notification_sound'] = 'Calm';
let comments: UserNotifyProps['comments'] = 'never';
let enableEmail: UserNotifyProps['email'] = 'true';
diff --git a/webapp/channels/src/components/widgets/modals/components/base_setting_item.scss b/webapp/channels/src/components/widgets/modals/components/base_setting_item.scss
index b00b72d518..bc9f3111f9 100644
--- a/webapp/channels/src/components/widgets/modals/components/base_setting_item.scss
+++ b/webapp/channels/src/components/widgets/modals/components/base_setting_item.scss
@@ -50,6 +50,11 @@
&__content {
display: flex;
flex-direction: column;
+
+ &.inline {
+ flex-direction: row;
+ gap: 24px;
+ }
}
&__fieldset-checkbox-ctr {
@@ -90,9 +95,10 @@
line-height: 20px;
}
- // Todo: add styling for react select
- //&__fieldset-react-select{
- //}
+ &__fieldset-react-select {
+ max-width: 240px;
+ flex-grow: 1;
+ }
&__input-checkbox {
width: 1.6rem;
diff --git a/webapp/channels/src/components/widgets/modals/components/base_setting_item.tsx b/webapp/channels/src/components/widgets/modals/components/base_setting_item.tsx
index 1cb5b5bdc7..60fd4e6734 100644
--- a/webapp/channels/src/components/widgets/modals/components/base_setting_item.tsx
+++ b/webapp/channels/src/components/widgets/modals/components/base_setting_item.tsx
@@ -23,11 +23,12 @@ export type BaseSettingItemProps = {
type Props = BaseSettingItemProps & {
content: JSX.Element;
+ isContentInline?: boolean;
className?: string;
descriptionAboveContent?: boolean;
}
-function BaseSettingItem({title, description, content, className, error, descriptionAboveContent = false}: Props): JSX.Element {
+function BaseSettingItem({title, description, content, className, error, descriptionAboveContent = false, isContentInline = false}: Props): JSX.Element {
const {formatMessage} = useIntl();
const titleComponent = title && (
@@ -64,7 +65,9 @@ function BaseSettingItem({title, description, content, className, error, descrip
{descriptionAboveContent ? descriptionComponent : undefined}
{content}
diff --git a/webapp/channels/src/components/widgets/modals/components/checkbox_with_select_item.tsx b/webapp/channels/src/components/widgets/modals/components/checkbox_with_select_item.tsx
new file mode 100644
index 0000000000..b43e336f1b
--- /dev/null
+++ b/webapp/channels/src/components/widgets/modals/components/checkbox_with_select_item.tsx
@@ -0,0 +1,101 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import type {ReactNode} from 'react';
+import React from 'react';
+import ReactSelect from 'react-select';
+import type {ValueType} from 'react-select';
+
+import type {BaseSettingItemProps} from './base_setting_item';
+import BaseSettingItem from './base_setting_item';
+import type {FieldsetCheckbox} from './checkbox_setting_item';
+import type {FieldsetReactSelect, Option} from './react_select_item';
+
+type Props = BaseSettingItemProps & {
+ containerClassName?: string;
+ descriptionAboveContent?: boolean;
+ checkboxFieldTitle: ReactNode;
+ checkboxFieldData: FieldsetCheckbox;
+ checkboxFieldValue: boolean;
+ handleCheckboxChange: (e: boolean) => void;
+ selectFieldData: FieldsetReactSelect;
+ selectFieldValue?: Option;
+ handleSelectChange: (selected: ValueType