mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
MM-38745 feat: Add dont clear option for DND (#26334)
* feat: Add dont clear option for DND * Merge conflicts fixed * ci: lint, types, i18n fix * refactor: Remove unused code --------- Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
parent
9e5d486a5b
commit
1dc5c006f2
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
describe('DND Status - Setting Your Own DND Status', () => {
|
describe('DND Status - Setting Your Own DND Status', () => {
|
||||||
const dndTimes = [
|
const dndTimes = [
|
||||||
|
'dndTime-dont_clear_menuitem',
|
||||||
'dndTime-thirty_minutes_menuitem',
|
'dndTime-thirty_minutes_menuitem',
|
||||||
'dndTime-one_hour_menuitem',
|
'dndTime-one_hour_menuitem',
|
||||||
'dndTime-two_hours_menuitem',
|
'dndTime-two_hours_menuitem',
|
||||||
@ -29,7 +30,7 @@ describe('DND Status - Setting Your Own DND Status', () => {
|
|||||||
|
|
||||||
it('MM-8497_1 Set status DND with predefined end times', () => {
|
it('MM-8497_1 Set status DND with predefined end times', () => {
|
||||||
// # Loop through all predefined end times and verify them
|
// # Loop through all predefined end times and verify them
|
||||||
for (let i = 0; i < 4; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
// # Open status dropdown menu and hover over Do Not Disturb option
|
// # Open status dropdown menu and hover over Do Not Disturb option
|
||||||
openDndStatusSubMenu();
|
openDndStatusSubMenu();
|
||||||
|
|
||||||
|
@ -102,6 +102,7 @@ exports[`components/StatusDropdown should match snapshot in default state 1`] =
|
|||||||
text="Away"
|
text="Away"
|
||||||
/>
|
/>
|
||||||
<SubMenuItem
|
<SubMenuItem
|
||||||
|
action={[Function]}
|
||||||
ariaLabel="Do not disturb. Disables all notifications"
|
ariaLabel="Do not disturb. Disables all notifications"
|
||||||
direction="left"
|
direction="left"
|
||||||
extraText="Disables all notifications"
|
extraText="Disables all notifications"
|
||||||
@ -122,7 +123,13 @@ exports[`components/StatusDropdown should match snapshot in default state 1`] =
|
|||||||
"direction": "right",
|
"direction": "right",
|
||||||
"id": "dndSubMenu-header",
|
"id": "dndSubMenu-header",
|
||||||
"isHeader": true,
|
"isHeader": true,
|
||||||
"text": "Disable notifications until:",
|
"text": "Clear after:",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"action": [Function],
|
||||||
|
"direction": "right",
|
||||||
|
"id": "dndTime-dont_clear",
|
||||||
|
"text": "Don't clear",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"action": [Function],
|
"action": [Function],
|
||||||
@ -168,7 +175,7 @@ exports[`components/StatusDropdown should match snapshot in default state 1`] =
|
|||||||
"action": [Function],
|
"action": [Function],
|
||||||
"direction": "right",
|
"direction": "right",
|
||||||
"id": "dndTime-custom",
|
"id": "dndTime-custom",
|
||||||
"text": "Custom",
|
"text": "Choose date and time",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -423,6 +430,7 @@ exports[`components/StatusDropdown should match snapshot with custom status and
|
|||||||
text="Away"
|
text="Away"
|
||||||
/>
|
/>
|
||||||
<SubMenuItem
|
<SubMenuItem
|
||||||
|
action={[Function]}
|
||||||
ariaLabel="Do not disturb. Disables all notifications"
|
ariaLabel="Do not disturb. Disables all notifications"
|
||||||
direction="left"
|
direction="left"
|
||||||
extraText="Disables all notifications"
|
extraText="Disables all notifications"
|
||||||
@ -443,7 +451,13 @@ exports[`components/StatusDropdown should match snapshot with custom status and
|
|||||||
"direction": "right",
|
"direction": "right",
|
||||||
"id": "dndSubMenu-header",
|
"id": "dndSubMenu-header",
|
||||||
"isHeader": true,
|
"isHeader": true,
|
||||||
"text": "Disable notifications until:",
|
"text": "Clear after:",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"action": [Function],
|
||||||
|
"direction": "right",
|
||||||
|
"id": "dndTime-dont_clear",
|
||||||
|
"text": "Don't clear",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"action": [Function],
|
"action": [Function],
|
||||||
@ -489,7 +503,7 @@ exports[`components/StatusDropdown should match snapshot with custom status and
|
|||||||
"action": [Function],
|
"action": [Function],
|
||||||
"direction": "right",
|
"direction": "right",
|
||||||
"id": "dndTime-custom",
|
"id": "dndTime-custom",
|
||||||
"text": "Custom",
|
"text": "Choose date and time",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -696,6 +710,7 @@ exports[`components/StatusDropdown should match snapshot with custom status enab
|
|||||||
text="Away"
|
text="Away"
|
||||||
/>
|
/>
|
||||||
<SubMenuItem
|
<SubMenuItem
|
||||||
|
action={[Function]}
|
||||||
ariaLabel="Do not disturb. Disables all notifications"
|
ariaLabel="Do not disturb. Disables all notifications"
|
||||||
direction="left"
|
direction="left"
|
||||||
extraText="Disables all notifications"
|
extraText="Disables all notifications"
|
||||||
@ -716,7 +731,13 @@ exports[`components/StatusDropdown should match snapshot with custom status enab
|
|||||||
"direction": "right",
|
"direction": "right",
|
||||||
"id": "dndSubMenu-header",
|
"id": "dndSubMenu-header",
|
||||||
"isHeader": true,
|
"isHeader": true,
|
||||||
"text": "Disable notifications until:",
|
"text": "Clear after:",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"action": [Function],
|
||||||
|
"direction": "right",
|
||||||
|
"id": "dndTime-dont_clear",
|
||||||
|
"text": "Don't clear",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"action": [Function],
|
"action": [Function],
|
||||||
@ -762,7 +783,7 @@ exports[`components/StatusDropdown should match snapshot with custom status enab
|
|||||||
"action": [Function],
|
"action": [Function],
|
||||||
"direction": "right",
|
"direction": "right",
|
||||||
"id": "dndTime-custom",
|
"id": "dndTime-custom",
|
||||||
"text": "Custom",
|
"text": "Choose date and time",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -969,6 +990,7 @@ exports[`components/StatusDropdown should match snapshot with custom status expi
|
|||||||
text="Away"
|
text="Away"
|
||||||
/>
|
/>
|
||||||
<SubMenuItem
|
<SubMenuItem
|
||||||
|
action={[Function]}
|
||||||
ariaLabel="Do not disturb. Disables all notifications"
|
ariaLabel="Do not disturb. Disables all notifications"
|
||||||
direction="left"
|
direction="left"
|
||||||
extraText="Disables all notifications"
|
extraText="Disables all notifications"
|
||||||
@ -989,7 +1011,13 @@ exports[`components/StatusDropdown should match snapshot with custom status expi
|
|||||||
"direction": "right",
|
"direction": "right",
|
||||||
"id": "dndSubMenu-header",
|
"id": "dndSubMenu-header",
|
||||||
"isHeader": true,
|
"isHeader": true,
|
||||||
"text": "Disable notifications until:",
|
"text": "Clear after:",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"action": [Function],
|
||||||
|
"direction": "right",
|
||||||
|
"id": "dndTime-dont_clear",
|
||||||
|
"text": "Don't clear",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"action": [Function],
|
"action": [Function],
|
||||||
@ -1035,7 +1063,7 @@ exports[`components/StatusDropdown should match snapshot with custom status expi
|
|||||||
"action": [Function],
|
"action": [Function],
|
||||||
"direction": "right",
|
"direction": "right",
|
||||||
"id": "dndTime-custom",
|
"id": "dndTime-custom",
|
||||||
"text": "Custom",
|
"text": "Choose date and time",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1243,6 +1271,7 @@ exports[`components/StatusDropdown should match snapshot with custom status puls
|
|||||||
text="Away"
|
text="Away"
|
||||||
/>
|
/>
|
||||||
<SubMenuItem
|
<SubMenuItem
|
||||||
|
action={[Function]}
|
||||||
ariaLabel="Do not disturb. Disables all notifications"
|
ariaLabel="Do not disturb. Disables all notifications"
|
||||||
direction="left"
|
direction="left"
|
||||||
extraText="Disables all notifications"
|
extraText="Disables all notifications"
|
||||||
@ -1263,7 +1292,13 @@ exports[`components/StatusDropdown should match snapshot with custom status puls
|
|||||||
"direction": "right",
|
"direction": "right",
|
||||||
"id": "dndSubMenu-header",
|
"id": "dndSubMenu-header",
|
||||||
"isHeader": true,
|
"isHeader": true,
|
||||||
"text": "Disable notifications until:",
|
"text": "Clear after:",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"action": [Function],
|
||||||
|
"direction": "right",
|
||||||
|
"id": "dndTime-dont_clear",
|
||||||
|
"text": "Don't clear",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"action": [Function],
|
"action": [Function],
|
||||||
@ -1309,7 +1344,7 @@ exports[`components/StatusDropdown should match snapshot with custom status puls
|
|||||||
"action": [Function],
|
"action": [Function],
|
||||||
"direction": "right",
|
"direction": "right",
|
||||||
"id": "dndTime-custom",
|
"id": "dndTime-custom",
|
||||||
"text": "Custom",
|
"text": "Choose date and time",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1492,6 +1527,7 @@ exports[`components/StatusDropdown should match snapshot with profile picture UR
|
|||||||
text="Away"
|
text="Away"
|
||||||
/>
|
/>
|
||||||
<SubMenuItem
|
<SubMenuItem
|
||||||
|
action={[Function]}
|
||||||
ariaLabel="Do not disturb. Disables all notifications"
|
ariaLabel="Do not disturb. Disables all notifications"
|
||||||
direction="left"
|
direction="left"
|
||||||
extraText="Disables all notifications"
|
extraText="Disables all notifications"
|
||||||
@ -1512,7 +1548,13 @@ exports[`components/StatusDropdown should match snapshot with profile picture UR
|
|||||||
"direction": "right",
|
"direction": "right",
|
||||||
"id": "dndSubMenu-header",
|
"id": "dndSubMenu-header",
|
||||||
"isHeader": true,
|
"isHeader": true,
|
||||||
"text": "Disable notifications until:",
|
"text": "Clear after:",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"action": [Function],
|
||||||
|
"direction": "right",
|
||||||
|
"id": "dndTime-dont_clear",
|
||||||
|
"text": "Don't clear",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"action": [Function],
|
"action": [Function],
|
||||||
@ -1558,7 +1600,7 @@ exports[`components/StatusDropdown should match snapshot with profile picture UR
|
|||||||
"action": [Function],
|
"action": [Function],
|
||||||
"direction": "right",
|
"direction": "right",
|
||||||
"id": "dndTime-custom",
|
"id": "dndTime-custom",
|
||||||
"text": "Custom",
|
"text": "Choose date and time",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1733,6 +1775,7 @@ exports[`components/StatusDropdown should match snapshot with status dropdown op
|
|||||||
text="Away"
|
text="Away"
|
||||||
/>
|
/>
|
||||||
<SubMenuItem
|
<SubMenuItem
|
||||||
|
action={[Function]}
|
||||||
ariaLabel="Do not disturb. Disables all notifications"
|
ariaLabel="Do not disturb. Disables all notifications"
|
||||||
direction="left"
|
direction="left"
|
||||||
extraText="Disables all notifications"
|
extraText="Disables all notifications"
|
||||||
@ -1753,7 +1796,13 @@ exports[`components/StatusDropdown should match snapshot with status dropdown op
|
|||||||
"direction": "right",
|
"direction": "right",
|
||||||
"id": "dndSubMenu-header",
|
"id": "dndSubMenu-header",
|
||||||
"isHeader": true,
|
"isHeader": true,
|
||||||
"text": "Disable notifications until:",
|
"text": "Clear after:",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"action": [Function],
|
||||||
|
"direction": "right",
|
||||||
|
"id": "dndTime-dont_clear",
|
||||||
|
"text": "Don't clear",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"action": [Function],
|
"action": [Function],
|
||||||
@ -1799,7 +1848,7 @@ exports[`components/StatusDropdown should match snapshot with status dropdown op
|
|||||||
"action": [Function],
|
"action": [Function],
|
||||||
"direction": "right",
|
"direction": "right",
|
||||||
"id": "dndTime-custom",
|
"id": "dndTime-custom",
|
||||||
"text": "Custom",
|
"text": "Choose date and time",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -2006,6 +2055,7 @@ exports[`components/StatusDropdown should not show clear status button when cust
|
|||||||
text="Away"
|
text="Away"
|
||||||
/>
|
/>
|
||||||
<SubMenuItem
|
<SubMenuItem
|
||||||
|
action={[Function]}
|
||||||
ariaLabel="Do not disturb. Disables all notifications"
|
ariaLabel="Do not disturb. Disables all notifications"
|
||||||
direction="left"
|
direction="left"
|
||||||
extraText="Disables all notifications"
|
extraText="Disables all notifications"
|
||||||
@ -2026,7 +2076,13 @@ exports[`components/StatusDropdown should not show clear status button when cust
|
|||||||
"direction": "right",
|
"direction": "right",
|
||||||
"id": "dndSubMenu-header",
|
"id": "dndSubMenu-header",
|
||||||
"isHeader": true,
|
"isHeader": true,
|
||||||
"text": "Disable notifications until:",
|
"text": "Clear after:",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"action": [Function],
|
||||||
|
"direction": "right",
|
||||||
|
"id": "dndTime-dont_clear",
|
||||||
|
"text": "Don't clear",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"action": [Function],
|
"action": [Function],
|
||||||
@ -2072,7 +2128,7 @@ exports[`components/StatusDropdown should not show clear status button when cust
|
|||||||
"action": [Function],
|
"action": [Function],
|
||||||
"direction": "right",
|
"direction": "right",
|
||||||
"id": "dndTime-custom",
|
"id": "dndTime-custom",
|
||||||
"text": "Custom",
|
"text": "Choose date and time",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -2327,6 +2383,7 @@ exports[`components/StatusDropdown should show clear status button when custom s
|
|||||||
text="Away"
|
text="Away"
|
||||||
/>
|
/>
|
||||||
<SubMenuItem
|
<SubMenuItem
|
||||||
|
action={[Function]}
|
||||||
ariaLabel="Do not disturb. Disables all notifications"
|
ariaLabel="Do not disturb. Disables all notifications"
|
||||||
direction="left"
|
direction="left"
|
||||||
extraText="Disables all notifications"
|
extraText="Disables all notifications"
|
||||||
@ -2347,7 +2404,13 @@ exports[`components/StatusDropdown should show clear status button when custom s
|
|||||||
"direction": "right",
|
"direction": "right",
|
||||||
"id": "dndSubMenu-header",
|
"id": "dndSubMenu-header",
|
||||||
"isHeader": true,
|
"isHeader": true,
|
||||||
"text": "Disable notifications until:",
|
"text": "Clear after:",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"action": [Function],
|
||||||
|
"direction": "right",
|
||||||
|
"id": "dndTime-dont_clear",
|
||||||
|
"text": "Don't clear",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"action": [Function],
|
"action": [Function],
|
||||||
@ -2393,7 +2456,7 @@ exports[`components/StatusDropdown should show clear status button when custom s
|
|||||||
"action": [Function],
|
"action": [Function],
|
||||||
"direction": "right",
|
"direction": "right",
|
||||||
"id": "dndTime-custom",
|
"id": "dndTime-custom",
|
||||||
"text": "Custom",
|
"text": "Choose date and time",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import {Client4} from 'mattermost-redux/client';
|
|||||||
import {Preferences} from 'mattermost-redux/constants';
|
import {Preferences} from 'mattermost-redux/constants';
|
||||||
import {get, getBool, getInt} from 'mattermost-redux/selectors/entities/preferences';
|
import {get, getBool, getInt} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
import {getCurrentTimezone} from 'mattermost-redux/selectors/entities/timezone';
|
import {getCurrentTimezone} from 'mattermost-redux/selectors/entities/timezone';
|
||||||
import {getCurrentUser, getStatusForUserId} from 'mattermost-redux/selectors/entities/users';
|
import {getCurrentUser, getDndEndTimeForUserId, getStatusForUserId} from 'mattermost-redux/selectors/entities/users';
|
||||||
|
|
||||||
import {openModal} from 'actions/views/modals';
|
import {openModal} from 'actions/views/modals';
|
||||||
import {setStatusDropdown} from 'actions/views/status_dropdown';
|
import {setStatusDropdown} from 'actions/views/status_dropdown';
|
||||||
@ -53,6 +53,7 @@ function makeMapStateToProps() {
|
|||||||
showCustomStatusPulsatingDot: showStatusDropdownPulsatingDot(state),
|
showCustomStatusPulsatingDot: showStatusDropdownPulsatingDot(state),
|
||||||
showCompleteYourProfileTour,
|
showCompleteYourProfileTour,
|
||||||
timezone: getCurrentTimezone(state),
|
timezone: getCurrentTimezone(state),
|
||||||
|
dndEndTime: getDndEndTimeForUserId(state, userId),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import moment from 'moment-timezone';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type {ReactNode} from 'react';
|
import type {ReactNode} from 'react';
|
||||||
import {injectIntl, FormattedDate, FormattedMessage, FormattedTime, defineMessage, defineMessages} from 'react-intl';
|
import {injectIntl, FormattedDate, FormattedMessage, FormattedTime, defineMessage, defineMessages} from 'react-intl';
|
||||||
@ -35,7 +36,7 @@ import Avatar from 'components/widgets/users/avatar/avatar';
|
|||||||
import type {TAvatarSizeToken} from 'components/widgets/users/avatar/avatar';
|
import type {TAvatarSizeToken} from 'components/widgets/users/avatar/avatar';
|
||||||
|
|
||||||
import {Constants, ModalIdentifiers, UserStatuses} from 'utils/constants';
|
import {Constants, ModalIdentifiers, UserStatuses} from 'utils/constants';
|
||||||
import {getCurrentDateTimeForTimezone, getCurrentMomentForTimezone} from 'utils/timezone';
|
import {getBrowserTimezone, getCurrentDateTimeForTimezone, getCurrentMomentForTimezone} from 'utils/timezone';
|
||||||
|
|
||||||
import type {ModalData} from 'types/actions';
|
import type {ModalData} from 'types/actions';
|
||||||
import type {Menu as MenuType} from 'types/store/plugins';
|
import type {Menu as MenuType} from 'types/store/plugins';
|
||||||
@ -64,6 +65,7 @@ type Props = {
|
|||||||
showCompleteYourProfileTour: boolean;
|
showCompleteYourProfileTour: boolean;
|
||||||
showCustomStatusPulsatingDot: boolean;
|
showCustomStatusPulsatingDot: boolean;
|
||||||
timezone?: string;
|
timezone?: string;
|
||||||
|
dndEndTime?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
@ -100,10 +102,6 @@ export const statusDropdownMessages: Record<string, Record<string, MessageDescri
|
|||||||
id: 'status_dropdown.set_dnd',
|
id: 'status_dropdown.set_dnd',
|
||||||
defaultMessage: 'Do not disturb',
|
defaultMessage: 'Do not disturb',
|
||||||
},
|
},
|
||||||
extra: {
|
|
||||||
id: 'status_dropdown.set_dnd.extra',
|
|
||||||
defaultMessage: 'Disables all notifications',
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
offline: defineMessages({
|
offline: defineMessages({
|
||||||
name: {
|
name: {
|
||||||
@ -115,11 +113,12 @@ export const statusDropdownMessages: Record<string, Record<string, MessageDescri
|
|||||||
|
|
||||||
export class StatusDropdown extends React.PureComponent<Props, State> {
|
export class StatusDropdown extends React.PureComponent<Props, State> {
|
||||||
dndTimes = [
|
dndTimes = [
|
||||||
|
{id: 'dont_clear', label: defineMessage({id: 'status_dropdown.dnd_sub_menu_item.dont_clear', defaultMessage: 'Don\'t clear'})},
|
||||||
{id: 'thirty_minutes', label: defineMessage({id: 'status_dropdown.dnd_sub_menu_item.thirty_minutes', defaultMessage: '30 mins'})},
|
{id: 'thirty_minutes', label: defineMessage({id: 'status_dropdown.dnd_sub_menu_item.thirty_minutes', defaultMessage: '30 mins'})},
|
||||||
{id: 'one_hour', label: defineMessage({id: 'status_dropdown.dnd_sub_menu_item.one_hour', defaultMessage: '1 hour'})},
|
{id: 'one_hour', label: defineMessage({id: 'status_dropdown.dnd_sub_menu_item.one_hour', defaultMessage: '1 hour'})},
|
||||||
{id: 'two_hours', label: defineMessage({id: 'status_dropdown.dnd_sub_menu_item.two_hours', defaultMessage: '2 hours'})},
|
{id: 'two_hours', label: defineMessage({id: 'status_dropdown.dnd_sub_menu_item.two_hours', defaultMessage: '2 hours'})},
|
||||||
{id: 'tomorrow', label: defineMessage({id: 'status_dropdown.dnd_sub_menu_item.tomorrow', defaultMessage: 'Tomorrow'})},
|
{id: 'tomorrow', label: defineMessage({id: 'status_dropdown.dnd_sub_menu_item.tomorrow', defaultMessage: 'Tomorrow'})},
|
||||||
{id: 'custom', label: defineMessage({id: 'status_dropdown.dnd_sub_menu_item.custom', defaultMessage: 'Custom'})},
|
{id: 'custom', label: defineMessage({id: 'status_dropdown.dnd_sub_menu_item.custom', defaultMessage: 'Choose date and time'})},
|
||||||
];
|
];
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
userId: '',
|
userId: '',
|
||||||
@ -176,18 +175,21 @@ export class StatusDropdown extends React.PureComponent<Props, State> {
|
|||||||
let endTime = currentDate;
|
let endTime = currentDate;
|
||||||
switch (index) {
|
switch (index) {
|
||||||
case 0:
|
case 0:
|
||||||
|
endTime = moment(0);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
// add 30 minutes in current time
|
// add 30 minutes in current time
|
||||||
endTime = currentDate.add(30, 'minutes');
|
endTime = currentDate.add(30, 'minutes');
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 2:
|
||||||
// add 1 hour in current time
|
// add 1 hour in current time
|
||||||
endTime = currentDate.add(1, 'hour');
|
endTime = currentDate.add(1, 'hour');
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 3:
|
||||||
// add 2 hours in current time
|
// add 2 hours in current time
|
||||||
endTime = currentDate.add(2, 'hours');
|
endTime = currentDate.add(2, 'hours');
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 4:
|
||||||
// set to next day 9 in the morning
|
// set to next day 9 in the morning
|
||||||
endTime = currentDate.add(1, 'day').set({hour: 9, minute: 0});
|
endTime = currentDate.add(1, 'day').set({hour: 9, minute: 0});
|
||||||
break;
|
break;
|
||||||
@ -381,10 +383,55 @@ export class StatusDropdown extends React.PureComponent<Props, State> {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
renderDndExtraText = (dndEndTime?: number, timezone?: string) => {
|
||||||
|
if (!(dndEndTime && dndEndTime > 0)) {
|
||||||
|
return this.props.intl.formatMessage({id: 'status_dropdown.set_dnd.extra', defaultMessage: 'Disables all notifications'});
|
||||||
|
}
|
||||||
|
|
||||||
|
const tz = timezone || getBrowserTimezone();
|
||||||
|
const currentTime = moment().tz(tz);
|
||||||
|
const endTime = moment.unix(dndEndTime).tz(tz);
|
||||||
|
|
||||||
|
let formattedEndTime;
|
||||||
|
|
||||||
|
const diffDays = endTime.clone().startOf('day').diff(currentTime.clone().startOf('day'), 'days');
|
||||||
|
|
||||||
|
switch (diffDays) {
|
||||||
|
case 0:
|
||||||
|
formattedEndTime = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='custom_status.expiry.until'
|
||||||
|
defaultMessage='Until {time}'
|
||||||
|
values={{time: endTime.format('h:mm A')}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
formattedEndTime = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='custom_status.expiry.until_tomorrow'
|
||||||
|
defaultMessage='Until Tomorrow {time}'
|
||||||
|
values={{time: endTime.format('h:mm A')}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
formattedEndTime = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='custom_status.expiry.until'
|
||||||
|
defaultMessage='Until {time}'
|
||||||
|
values={{time: endTime.format('lll')}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedEndTime;
|
||||||
|
};
|
||||||
|
|
||||||
render = (): JSX.Element => {
|
render = (): JSX.Element => {
|
||||||
const {intl} = this.props;
|
const {intl} = this.props;
|
||||||
const needsConfirm = this.isUserOutOfOffice() && this.props.autoResetPref === '';
|
const needsConfirm = this.isUserOutOfOffice() && this.props.autoResetPref === '';
|
||||||
const {status, customStatus, isCustomStatusExpired, currentUser} = this.props;
|
const {status, customStatus, isCustomStatusExpired, currentUser, timezone, dndEndTime} = this.props;
|
||||||
const isStatusSet = customStatus && !isCustomStatusExpired && (customStatus.text?.length > 0 || customStatus.emoji?.length > 0);
|
const isStatusSet = customStatus && !isCustomStatusExpired && (customStatus.text?.length > 0 || customStatus.emoji?.length > 0);
|
||||||
|
|
||||||
const setOnline = needsConfirm ? () => this.showStatusChangeConfirmation('online') : this.setOnline;
|
const setOnline = needsConfirm ? () => this.showStatusChangeConfirmation('online') : this.setOnline;
|
||||||
@ -404,13 +451,13 @@ export class StatusDropdown extends React.PureComponent<Props, State> {
|
|||||||
{
|
{
|
||||||
id: 'dndSubMenu-header',
|
id: 'dndSubMenu-header',
|
||||||
direction: 'right',
|
direction: 'right',
|
||||||
text: this.props.intl.formatMessage({id: 'status_dropdown.dnd_sub_menu_header', defaultMessage: 'Disable notifications until:'}),
|
text: this.props.intl.formatMessage({id: 'status_dropdown.dnd_sub_menu_header', defaultMessage: 'Clear after:'}),
|
||||||
isHeader: true,
|
isHeader: true,
|
||||||
},
|
},
|
||||||
] as MenuType[])?.concat(
|
] as MenuType[])?.concat(
|
||||||
this.dndTimes.map<MenuType>(({id, label}, index) => {
|
this.dndTimes.map<MenuType>(({id, label}, index) => {
|
||||||
let text: MenuType['text'] = this.props.intl.formatMessage(label);
|
let text: MenuType['text'] = this.props.intl.formatMessage(label);
|
||||||
if (index === 3) {
|
if (index === 4) {
|
||||||
const tomorrow = getCurrentMomentForTimezone(this.props.timezone).add(1, 'day').set({hour: 9, minute: 0}).toDate();
|
const tomorrow = getCurrentMomentForTimezone(this.props.timezone).add(1, 'day').set({hour: 9, minute: 0}).toDate();
|
||||||
text = (
|
text = (
|
||||||
<>
|
<>
|
||||||
@ -437,7 +484,7 @@ export class StatusDropdown extends React.PureComponent<Props, State> {
|
|||||||
direction: 'right',
|
direction: 'right',
|
||||||
text,
|
text,
|
||||||
action:
|
action:
|
||||||
index === 4 ? () => setCustomTimedDnd() : () => setDnd(index),
|
index === 5 ? () => setCustomTimedDnd() : () => setDnd(index),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -483,6 +530,8 @@ export class StatusDropdown extends React.PureComponent<Props, State> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dndExtraText = this.renderDndExtraText(dndEndTime, timezone);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuWrapper
|
<MenuWrapper
|
||||||
onToggle={this.onToggle}
|
onToggle={this.onToggle}
|
||||||
@ -575,9 +624,9 @@ export class StatusDropdown extends React.PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
<Menu.ItemSubMenu
|
<Menu.ItemSubMenu
|
||||||
subMenu={dndSubMenuItems}
|
subMenu={dndSubMenuItems}
|
||||||
ariaLabel={`${this.props.intl.formatMessage(statusDropdownMessages.dnd.name)}. ${this.props.intl.formatMessage(statusDropdownMessages.dnd.extra)}`}
|
ariaLabel={`${this.props.intl.formatMessage(statusDropdownMessages.dnd.name)}. ${dndExtraText}`}
|
||||||
text={this.props.intl.formatMessage(statusDropdownMessages.dnd.name)}
|
text={this.props.intl.formatMessage(statusDropdownMessages.dnd.name)}
|
||||||
extraText={this.props.intl.formatMessage(statusDropdownMessages.dnd.extra)}
|
extraText={dndExtraText}
|
||||||
icon={(
|
icon={(
|
||||||
<StatusIcon
|
<StatusIcon
|
||||||
status={'dnd'}
|
status={'dnd'}
|
||||||
@ -588,6 +637,7 @@ export class StatusDropdown extends React.PureComponent<Props, State> {
|
|||||||
direction={'left'}
|
direction={'left'}
|
||||||
openUp={this.state.openUp}
|
openUp={this.state.openUp}
|
||||||
id={'status-menu-dnd'}
|
id={'status-menu-dnd'}
|
||||||
|
action={() => setDnd(0)}
|
||||||
/>
|
/>
|
||||||
<Menu.ItemAction
|
<Menu.ItemAction
|
||||||
onClick={setOffline}
|
onClick={setOffline}
|
||||||
|
@ -21,7 +21,6 @@ exports[`components/widgets/menu/menu_items/submenu_item empty subMenu should ma
|
|||||||
<div
|
<div
|
||||||
className=""
|
className=""
|
||||||
id="1"
|
id="1"
|
||||||
onClick={[Function]}
|
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
onMouseEnter={[Function]}
|
onMouseEnter={[Function]}
|
||||||
onMouseLeave={[Function]}
|
onMouseLeave={[Function]}
|
||||||
@ -89,7 +88,6 @@ exports[`components/widgets/menu/menu_items/submenu_item present subMenu should
|
|||||||
<div
|
<div
|
||||||
className=""
|
className=""
|
||||||
id="1"
|
id="1"
|
||||||
onClick={[Function]}
|
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
onMouseEnter={[Function]}
|
onMouseEnter={[Function]}
|
||||||
onMouseLeave={[Function]}
|
onMouseLeave={[Function]}
|
||||||
@ -142,7 +140,6 @@ exports[`components/widgets/menu/menu_items/submenu_item present subMenu should
|
|||||||
<div
|
<div
|
||||||
className=""
|
className=""
|
||||||
id="A"
|
id="A"
|
||||||
onClick={[Function]}
|
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
onMouseEnter={[Function]}
|
onMouseEnter={[Function]}
|
||||||
onMouseLeave={[Function]}
|
onMouseLeave={[Function]}
|
||||||
@ -199,7 +196,6 @@ exports[`components/widgets/menu/menu_items/submenu_item present subMenu should
|
|||||||
<div
|
<div
|
||||||
className=""
|
className=""
|
||||||
id="B"
|
id="B"
|
||||||
onClick={[Function]}
|
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
onMouseEnter={[Function]}
|
onMouseEnter={[Function]}
|
||||||
onMouseLeave={[Function]}
|
onMouseLeave={[Function]}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
import {mount} from 'enzyme';
|
import {mount} from 'enzyme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import {render, screen, userEvent} from 'tests/react_testing_utils';
|
||||||
import Constants from 'utils/constants';
|
import Constants from 'utils/constants';
|
||||||
|
|
||||||
import SubMenuItem from './submenu_item';
|
import SubMenuItem from './submenu_item';
|
||||||
@ -27,6 +28,7 @@ describe('components/widgets/menu/menu_items/submenu_item', () => {
|
|||||||
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('present subMenu should match snapshot with submenu', () => {
|
test('present subMenu should match snapshot with submenu', () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<SubMenuItem
|
<SubMenuItem
|
||||||
@ -52,11 +54,13 @@ describe('components/widgets/menu/menu_items/submenu_item', () => {
|
|||||||
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
test('test subMenu click triggers action', async () => {
|
|
||||||
const action1 = jest.fn().mockReturnValueOnce('default');
|
test('test subMenu click triggers action', () => {
|
||||||
const action2 = jest.fn().mockReturnValueOnce('default');
|
const action1 = jest.fn();
|
||||||
const action3 = jest.fn().mockReturnValueOnce('default');
|
const action2 = jest.fn();
|
||||||
const wrapper = mount(
|
const action3 = jest.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
<SubMenuItem
|
<SubMenuItem
|
||||||
key={'_pluginmenuitem'}
|
key={'_pluginmenuitem'}
|
||||||
id={'Z'}
|
id={'Z'}
|
||||||
@ -79,16 +83,20 @@ describe('components/widgets/menu/menu_items/submenu_item', () => {
|
|||||||
root={true}
|
root={true}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
wrapper.setState({show: true});
|
|
||||||
wrapper.find('#Z').at(1).simulate('click');
|
userEvent.click(screen.getByText('test'));
|
||||||
await expect(action1).toHaveBeenCalledTimes(1);
|
expect(action1).toHaveBeenCalledTimes(1);
|
||||||
wrapper.setState({show: true});
|
|
||||||
wrapper.find('#A').at(1).simulate('click');
|
userEvent.click(screen.getByText('Test A'));
|
||||||
await expect(action2).toHaveBeenCalledTimes(1);
|
expect(action2).toHaveBeenCalledTimes(1);
|
||||||
wrapper.setState({show: true});
|
|
||||||
wrapper.find('#B').at(1).simulate('click');
|
userEvent.click(screen.getByText('Test B'));
|
||||||
await expect(action3).toHaveBeenCalledTimes(1);
|
expect(action3).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// Confirm that the parent's action wasn't called again when clicking on a child item
|
||||||
|
expect(action1).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show/hide submenu based on keyboard commands', () => {
|
test('should show/hide submenu based on keyboard commands', () => {
|
||||||
const wrapper = mount<SubMenuItem>(
|
const wrapper = mount<SubMenuItem>(
|
||||||
<SubMenuItem
|
<SubMenuItem
|
||||||
|
@ -55,7 +55,7 @@ export type Props = {
|
|||||||
direction?: 'left' | 'right';
|
direction?: 'left' | 'right';
|
||||||
openUp?: boolean;
|
openUp?: boolean;
|
||||||
styleSelectableItem?: boolean;
|
styleSelectableItem?: boolean;
|
||||||
extraText?: string;
|
extraText?: string| JSX.Element;
|
||||||
rightDecorator?: React.ReactNode;
|
rightDecorator?: React.ReactNode;
|
||||||
isHeader?: boolean;
|
isHeader?: boolean;
|
||||||
tabIndex?: number;
|
tabIndex?: number;
|
||||||
@ -95,7 +95,7 @@ export default class SubMenuItem extends React.PureComponent<Props, State> {
|
|||||||
this.setState({show: false});
|
this.setState({show: false});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onClick = (event: React.SyntheticEvent<HTMLElement>) => {
|
private onClick = (event: React.SyntheticEvent<HTMLElement>| React.BaseSyntheticEvent<HTMLElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const {id, postId, subMenu, action, root, isHeader} = this.props;
|
const {id, postId, subMenu, action, root, isHeader} = this.props;
|
||||||
const isMobile = isMobileViewHack();
|
const isMobile = isMobileViewHack();
|
||||||
@ -112,9 +112,14 @@ export default class SubMenuItem extends React.PureComponent<Props, State> {
|
|||||||
} else if (action) { // leaf node in the tree handles action only
|
} else if (action) { // leaf node in the tree handles action only
|
||||||
action(postId);
|
action(postId);
|
||||||
}
|
}
|
||||||
} else if (event.currentTarget.id === id && action) {
|
} else {
|
||||||
|
const shouldCallAction =
|
||||||
|
(event.type === 'keydown' && event.currentTarget.id === id) ||
|
||||||
|
event.target.parentElement.id === id;
|
||||||
|
if (shouldCallAction && action) {
|
||||||
action(postId);
|
action(postId);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
@ -233,7 +238,6 @@ export default class SubMenuItem extends React.PureComponent<Props, State> {
|
|||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
onMouseEnter={this.show}
|
onMouseEnter={this.show}
|
||||||
onMouseLeave={this.hide}
|
onMouseLeave={this.hide}
|
||||||
onClick={this.onClick}
|
|
||||||
tabIndex={tabIndex ?? 0}
|
tabIndex={tabIndex ?? 0}
|
||||||
onKeyDown={this.handleKeyDown}
|
onKeyDown={this.handleKeyDown}
|
||||||
>
|
>
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {shallow, mount} from 'enzyme';
|
import {shallow} from 'enzyme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Modal} from 'react-bootstrap';
|
import {Modal} from 'react-bootstrap';
|
||||||
|
|
||||||
|
import {render, screen, userEvent} from 'tests/react_testing_utils';
|
||||||
|
|
||||||
import SubMenuModal from './submenu_modal';
|
import SubMenuModal from './submenu_modal';
|
||||||
|
|
||||||
jest.mock('../../is_mobile_view_hack', () => ({
|
jest.mock('../../is_mobile_view_hack', () => ({
|
||||||
@ -67,24 +69,19 @@ describe('components/submenu_modal', () => {
|
|||||||
const props = {
|
const props = {
|
||||||
...baseProps,
|
...baseProps,
|
||||||
};
|
};
|
||||||
const wrapper = mount(
|
|
||||||
|
render(
|
||||||
<SubMenuModal {...props}/>,
|
<SubMenuModal {...props}/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
wrapper.setState({show: true});
|
userEvent.click(screen.getByText('Text A'));
|
||||||
await wrapper.find('#A').at(1).simulate('click');
|
|
||||||
expect(action1).toHaveBeenCalledTimes(1);
|
expect(action1).toHaveBeenCalledTimes(1);
|
||||||
expect(wrapper.state('show')).toEqual(false);
|
|
||||||
|
|
||||||
wrapper.setState({show: true});
|
userEvent.click(screen.getByText('Text B'));
|
||||||
await wrapper.find('#B').at(1).simulate('click');
|
|
||||||
expect(action2).toHaveBeenCalledTimes(1);
|
expect(action2).toHaveBeenCalledTimes(1);
|
||||||
expect(wrapper.state('show')).toEqual(false);
|
|
||||||
|
|
||||||
wrapper.setState({show: true});
|
userEvent.click(screen.getByText('Text C'));
|
||||||
await wrapper.find('#C').at(1).simulate('click');
|
|
||||||
expect(action3).toHaveBeenCalledTimes(1);
|
expect(action3).toHaveBeenCalledTimes(1);
|
||||||
expect(wrapper.state('show')).toEqual(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should have called props.onExited when Modal.onExited is called', () => {
|
test('should have called props.onExited when Modal.onExited is called', () => {
|
||||||
|
@ -3433,7 +3433,8 @@
|
|||||||
"custom_status.expiry_dropdown.this_week": "This week",
|
"custom_status.expiry_dropdown.this_week": "This week",
|
||||||
"custom_status.expiry_dropdown.today": "Today",
|
"custom_status.expiry_dropdown.today": "Today",
|
||||||
"custom_status.expiry.time_picker.title": "Time",
|
"custom_status.expiry.time_picker.title": "Time",
|
||||||
"custom_status.expiry.until": "Until",
|
"custom_status.expiry.until": "Until {time}",
|
||||||
|
"custom_status.expiry.until_tomorrow": "Until Tomorrow {time}",
|
||||||
"custom_status.modal_cancel": "Clear Status",
|
"custom_status.modal_cancel": "Clear Status",
|
||||||
"custom_status.modal_confirm": "Set Status",
|
"custom_status.modal_confirm": "Set Status",
|
||||||
"custom_status.set_status": "Set a status",
|
"custom_status.set_status": "Set a status",
|
||||||
@ -5181,8 +5182,9 @@
|
|||||||
"start_trial.modal.loading": "Loading...",
|
"start_trial.modal.loading": "Loading...",
|
||||||
"start_trial.tutorialTip.desc": "Explore our most requested premium features. Determine user access with Guest Accounts, automate compliance reports, and send secure ID-only mobile push notifications.",
|
"start_trial.tutorialTip.desc": "Explore our most requested premium features. Determine user access with Guest Accounts, automate compliance reports, and send secure ID-only mobile push notifications.",
|
||||||
"start_trial.tutorialTip.title": "Try our premium features for free",
|
"start_trial.tutorialTip.title": "Try our premium features for free",
|
||||||
"status_dropdown.dnd_sub_menu_header": "Disable notifications until:",
|
"status_dropdown.dnd_sub_menu_header": "Clear after:",
|
||||||
"status_dropdown.dnd_sub_menu_item.custom": "Custom",
|
"status_dropdown.dnd_sub_menu_item.custom": "Choose date and time",
|
||||||
|
"status_dropdown.dnd_sub_menu_item.dont_clear": "Don't clear",
|
||||||
"status_dropdown.dnd_sub_menu_item.one_hour": "1 hour",
|
"status_dropdown.dnd_sub_menu_item.one_hour": "1 hour",
|
||||||
"status_dropdown.dnd_sub_menu_item.thirty_minutes": "30 mins",
|
"status_dropdown.dnd_sub_menu_item.thirty_minutes": "30 mins",
|
||||||
"status_dropdown.dnd_sub_menu_item.tomorrow": "Tomorrow",
|
"status_dropdown.dnd_sub_menu_item.tomorrow": "Tomorrow",
|
||||||
|
@ -1012,6 +1012,7 @@ describe('Reducers.users', () => {
|
|||||||
|
|
||||||
let state: UsersState = {
|
let state: UsersState = {
|
||||||
currentUserId: '',
|
currentUserId: '',
|
||||||
|
dndEndTimes: {},
|
||||||
mySessions: [],
|
mySessions: [],
|
||||||
myAudits: [],
|
myAudits: [],
|
||||||
myUserAccessTokens: {},
|
myUserAccessTokens: {},
|
||||||
@ -1060,6 +1061,7 @@ describe('Reducers.users', () => {
|
|||||||
|
|
||||||
expect(nextState).toEqual({
|
expect(nextState).toEqual({
|
||||||
currentUserId: '',
|
currentUserId: '',
|
||||||
|
dndEndTimes: {},
|
||||||
mySessions: [],
|
mySessions: [],
|
||||||
myAudits: [],
|
myAudits: [],
|
||||||
myUserAccessTokens: {},
|
myUserAccessTokens: {},
|
||||||
|
@ -496,6 +496,35 @@ function addToState<T>(state: Record<string, T>, key: string, value: T): Record<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dndEndTimes(state: RelationOneToOne<UserProfile, number> = {}, action: AnyAction) {
|
||||||
|
switch (action.type) {
|
||||||
|
case UserTypes.RECEIVED_STATUS: {
|
||||||
|
const userId = action.data.user_id;
|
||||||
|
const endTime = action.data.dnd_end_time;
|
||||||
|
|
||||||
|
return addToState(state, userId, endTime);
|
||||||
|
}
|
||||||
|
case UserTypes.RECEIVED_STATUSES: {
|
||||||
|
const userStatuses: UserStatus[] = action.data;
|
||||||
|
|
||||||
|
return userStatuses.reduce((nextState, userStatus) => addToState(nextState, userStatus.user_id, userStatus.dnd_end_time || 0), state);
|
||||||
|
}
|
||||||
|
case UserTypes.PROFILE_NO_LONGER_VISIBLE: {
|
||||||
|
if (state[action.data.user_id]) {
|
||||||
|
const newState = {...state};
|
||||||
|
delete newState[action.data.user_id];
|
||||||
|
return newState;
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
case UserTypes.LOGOUT_SUCCESS:
|
||||||
|
return {};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function statuses(state: RelationOneToOne<UserProfile, string> = {}, action: AnyAction) {
|
function statuses(state: RelationOneToOne<UserProfile, string> = {}, action: AnyAction) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case UserTypes.RECEIVED_STATUS: {
|
case UserTypes.RECEIVED_STATUS: {
|
||||||
@ -705,6 +734,9 @@ export default combineReducers({
|
|||||||
// object where every key is a group id and has a Set with the users id that are members of the group
|
// object where every key is a group id and has a Set with the users id that are members of the group
|
||||||
profilesNotInGroup,
|
profilesNotInGroup,
|
||||||
|
|
||||||
|
// object where every key is the user id and has a value with the dnd end time of each user
|
||||||
|
dndEndTimes,
|
||||||
|
|
||||||
// object where every key is the user id and has a value with the current status of each user
|
// object where every key is the user id and has a value with the current status of each user
|
||||||
statuses,
|
statuses,
|
||||||
|
|
||||||
|
@ -435,6 +435,10 @@ export function getStatusForUserId(state: GlobalState, userId: UserProfile['id']
|
|||||||
return getUserStatuses(state)[userId];
|
return getUserStatuses(state)[userId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getDndEndTimeForUserId(state: GlobalState, userId: UserProfile['id']): number {
|
||||||
|
return state.entities.users.dndEndTimes[userId];
|
||||||
|
}
|
||||||
|
|
||||||
export function getTotalUsersStats(state: GlobalState) {
|
export function getTotalUsersStats(state: GlobalState) {
|
||||||
return state.entities.users.stats;
|
return state.entities.users.stats;
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ const state: GlobalState = {
|
|||||||
filteredStats: {},
|
filteredStats: {},
|
||||||
myUserAccessTokens: {},
|
myUserAccessTokens: {},
|
||||||
lastActivity: {},
|
lastActivity: {},
|
||||||
|
dndEndTimes: {},
|
||||||
},
|
},
|
||||||
limits: {
|
limits: {
|
||||||
usersLimits: {
|
usersLimits: {
|
||||||
|
@ -23,6 +23,7 @@ const emptyOtherUsersState: Omit<GlobalState['entities']['users'], 'profiles' |
|
|||||||
filteredStats: {},
|
filteredStats: {},
|
||||||
myUserAccessTokens: {},
|
myUserAccessTokens: {},
|
||||||
lastActivity: {},
|
lastActivity: {},
|
||||||
|
dndEndTimes: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const adminUsersState: () => GlobalState['entities']['users'] = () => ({
|
export const adminUsersState: () => GlobalState['entities']['users'] = () => ({
|
||||||
|
@ -82,6 +82,7 @@ export type UsersState = {
|
|||||||
filteredStats: Partial<UsersStats>;
|
filteredStats: Partial<UsersStats>;
|
||||||
myUserAccessTokens: Record<string, UserAccessToken>;
|
myUserAccessTokens: Record<string, UserAccessToken>;
|
||||||
lastActivity: RelationOneToOne<UserProfile, number>;
|
lastActivity: RelationOneToOne<UserProfile, number>;
|
||||||
|
dndEndTimes: RelationOneToOne<UserProfile, number>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UserTimezone = {
|
export type UserTimezone = {
|
||||||
|
Loading…
Reference in New Issue
Block a user