diff --git a/webapp/actions/user_actions.jsx b/webapp/actions/user_actions.jsx
index 606dccdb81..63c716d5db 100644
--- a/webapp/actions/user_actions.jsx
+++ b/webapp/actions/user_actions.jsx
@@ -26,6 +26,7 @@ const dispatch = store.dispatch;
const getState = store.getState;
import * as Selectors from 'mattermost-redux/selectors/entities/users';
+import {getBool} from 'mattermost-redux/selectors/entities/preferences';
import {
getProfiles,
@@ -43,12 +44,15 @@ import {
createUser,
login,
loadMe as loadMeRedux,
- updateUserRoles as updateUserRolesRedux
+ updateUserRoles as updateUserRolesRedux,
+ getStatus,
+ setStatus
} from 'mattermost-redux/actions/users';
import {getClientConfig, getLicenseConfig} from 'mattermost-redux/actions/general';
import {getTeamMembersByIds, getMyTeamMembers} from 'mattermost-redux/actions/teams';
import {getChannelAndMyMember} from 'mattermost-redux/actions/channels';
+import {Preferences as PreferencesRedux} from 'mattermost-redux/constants';
export function loadMe(callback) {
loadMeRedux()(dispatch, getState).then(
@@ -831,3 +835,23 @@ export function loadMyTeamMembers() {
}
);
}
+
+export function autoResetStatus() {
+ return async (doDispatch, doGetState) => {
+ const {currentUserId} = getState().entities.users;
+ const userStatus = await getStatus(currentUserId)(doDispatch, doGetState);
+
+ if (!userStatus.manual) {
+ return userStatus;
+ }
+
+ const autoReset = getBool(getState(), PreferencesRedux.CATEGORY_AUTO_RESET_MANUAL_STATUS, currentUserId, false);
+
+ if (autoReset) {
+ setStatus({user_id: currentUserId, status: 'online'})(doDispatch, doGetState);
+ return userStatus;
+ }
+
+ return userStatus;
+ };
+}
diff --git a/webapp/components/admin_console/system_users/system_users_dropdown.jsx b/webapp/components/admin_console/system_users/system_users_dropdown.jsx
index 159e133e59..fe53ade443 100644
--- a/webapp/components/admin_console/system_users/system_users_dropdown.jsx
+++ b/webapp/components/admin_console/system_users/system_users_dropdown.jsx
@@ -202,7 +202,7 @@ export default class SystemUsersDropdown extends React.Component {
title={title}
message={message}
confirmButtonClass={confirmButtonClass}
- confirmButton={deactivateMemberButton}
+ confirmButtonText={deactivateMemberButton}
onConfirm={this.handleDeactivateMember}
onCancel={this.handleDeactivateCancel}
/>
@@ -467,7 +467,7 @@ export default class SystemUsersDropdown extends React.Component {
show={this.state.showDemoteModal}
title={title}
message={message}
- confirmButton={confirmButton}
+ confirmButtonText={confirmButton}
onConfirm={this.handleDemoteSubmit}
onCancel={this.handleDemoteCancel}
/>
diff --git a/webapp/components/channel_header.jsx b/webapp/components/channel_header.jsx
index b817cb57d5..7116b435c0 100644
--- a/webapp/components/channel_header.jsx
+++ b/webapp/components/channel_header.jsx
@@ -272,7 +272,7 @@ export default class ChannelHeader extends React.Component {
title={title}
message={message}
confirmButtonClass={buttonClass}
- confirmButton={button}
+ confirmButtonText={button}
onConfirm={() => ChannelActions.leaveChannel(this.state.channel.id)}
onCancel={this.hideLeaveChannelModal}
/>
diff --git a/webapp/components/confirm_modal.jsx b/webapp/components/confirm_modal.jsx
index 89656a776e..72f341efbd 100644
--- a/webapp/components/confirm_modal.jsx
+++ b/webapp/components/confirm_modal.jsx
@@ -1,18 +1,70 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import {FormattedMessage} from 'react-intl';
-import {Modal} from 'react-bootstrap';
-
-import PropTypes from 'prop-types';
-
import React from 'react';
+import {Modal} from 'react-bootstrap';
+import PropTypes from 'prop-types';
+import {FormattedMessage} from 'react-intl';
export default class ConfirmModal extends React.Component {
- constructor(props) {
- super(props);
+ static propTypes = {
- this.handleKeypress = this.handleKeypress.bind(this);
+ /*
+ * Set to show modal
+ */
+ show: PropTypes.bool.isRequired,
+
+ /*
+ * Title to use for the modal
+ */
+ title: PropTypes.node,
+
+ /*
+ * Message to display in the body of the modal
+ */
+ message: PropTypes.node,
+
+ /*
+ * The CSS class to apply to the confirm button
+ */
+ confirmButtonClass: PropTypes.string,
+
+ /*
+ * Text/jsx element on the confirm button
+ */
+ confirmButtonText: PropTypes.node,
+
+ /*
+ * Text/jsx element on the cancel button
+ */
+ cancelButtonText: PropTypes.node,
+
+ /*
+ * Set to show checkbox
+ */
+ showCheckbox: PropTypes.bool,
+
+ /*
+ * Text/jsx element to display with the checkbox
+ */
+ checkboxText: PropTypes.node,
+
+ /*
+ * Function called when the confirm button or ENTER is pressed. Passes `true` if the checkbox is checked
+ */
+ onConfirm: PropTypes.func.isRequired,
+
+ /*
+ * Function called when the cancel button is pressed or the modal is hidden. Passes `true` if the checkbox is checked
+ */
+ onCancel: PropTypes.func.isRequired
+ }
+
+ static defaultProps = {
+ title: '',
+ message: '',
+ confirmButtonClass: 'btn btn-primary',
+ confirmButtonText: ''
}
componentDidMount() {
@@ -33,13 +85,50 @@ export default class ConfirmModal extends React.Component {
}
}
- handleKeypress(e) {
+ handleKeypress = (e) => {
if (e.key === 'Enter' && this.props.show) {
- this.props.onConfirm(e);
+ this.handleConfirm();
}
}
+ handleConfirm = () => {
+ const checked = this.refs.checkbox ? this.refs.checkbox.checked : false;
+ this.props.onConfirm(checked);
+ }
+
+ handleCancel = () => {
+ const checked = this.refs.checkbox ? this.refs.checkbox.checked : false;
+ this.props.onCancel(checked);
+ }
+
render() {
+ let checkbox;
+ if (this.props.showCheckbox) {
+ checkbox = (
+
+
+
+ );
+ }
+
+ let cancelText;
+ if (this.props.cancelButtonText) {
+ cancelText = this.props.cancelButtonText;
+ } else {
+ cancelText = (
+
+ );
+ }
+
return (
{this.props.message}
+ {checkbox}
);
}
}
-
-ConfirmModal.defaultProps = {
- title: '',
- message: '',
- confirmButtonClass: 'btn btn-primary',
- confirmButton: ''
-};
-ConfirmModal.propTypes = {
- show: PropTypes.bool.isRequired,
- title: PropTypes.node,
- message: PropTypes.node,
- confirmButtonClass: PropTypes.string,
- confirmButton: PropTypes.node,
- onConfirm: PropTypes.func.isRequired,
- onCancel: PropTypes.func.isRequired
-};
diff --git a/webapp/components/create_post.jsx b/webapp/components/create_post.jsx
index 2735718e1c..31c19bd5c2 100644
--- a/webapp/components/create_post.jsx
+++ b/webapp/components/create_post.jsx
@@ -120,7 +120,9 @@ export default class CreatePost extends React.Component {
}
doSubmit(e) {
- e.preventDefault();
+ if (e) {
+ e.preventDefault();
+ }
const post = {};
post.file_ids = [];
diff --git a/webapp/components/delete_modal_trigger.jsx b/webapp/components/delete_modal_trigger.jsx
index 9a7222ec7a..54a1d6604f 100644
--- a/webapp/components/delete_modal_trigger.jsx
+++ b/webapp/components/delete_modal_trigger.jsx
@@ -28,8 +28,8 @@ export default class DeleteModalTrigger extends React.Component {
});
}
- handleConfirm(e) {
- this.props.onDelete(e);
+ handleConfirm() {
+ this.props.onDelete();
}
handleCancel() {
@@ -57,7 +57,7 @@ export default class DeleteModalTrigger extends React.Component {
show={this.state.showDeleteModal}
title={this.modalTitle}
message={this.modalMessage}
- confirmButton={this.modalConfirmButton}
+ confirmButtonText={this.modalConfirmButton}
onConfirm={this.handleConfirm}
onCancel={this.handleCancel}
onKeyDown={this.handleKeyDown}
diff --git a/webapp/components/integrations/components/edit_command.jsx b/webapp/components/integrations/components/edit_command.jsx
index f4c05e4eb4..ae10404638 100644
--- a/webapp/components/integrations/components/edit_command.jsx
+++ b/webapp/components/integrations/components/edit_command.jsx
@@ -719,7 +719,7 @@ export default class EditCommand extends React.Component {
this.setState({showConfirmModal: false})}
diff --git a/webapp/components/navbar.jsx b/webapp/components/navbar.jsx
index 6f8df3709f..e488145e1d 100644
--- a/webapp/components/navbar.jsx
+++ b/webapp/components/navbar.jsx
@@ -753,7 +753,7 @@ export default class Navbar extends React.Component {
title={title}
message={message}
confirmButtonClass={buttonClass}
- confirmButton={button}
+ confirmButtonText={button}
onConfirm={() => ChannelActions.leaveChannel(this.state.channel.id)}
onCancel={this.hideLeaveChannelModal}
/>
diff --git a/webapp/components/needs_team/needs_team.jsx b/webapp/components/needs_team/needs_team.jsx
index 95556fb316..4f5188a47e 100644
--- a/webapp/components/needs_team/needs_team.jsx
+++ b/webapp/components/needs_team/needs_team.jsx
@@ -1,9 +1,8 @@
-import PropTypes from 'prop-types';
-
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
+import PropTypes from 'prop-types';
import $ from 'jquery';
@@ -42,6 +41,7 @@ import RemovedFromChannelModal from 'components/removed_from_channel_modal.jsx';
import ImportThemeModal from 'components/user_settings/import_theme_modal.jsx';
import InviteMemberModal from 'components/invite_member_modal.jsx';
import LeaveTeamModal from 'components/leave_team_modal.jsx';
+import ResetStatusModal from 'components/reset_status_modal';
import iNoBounce from 'inobounce';
import * as UserAgent from 'utils/user_agent.jsx';
@@ -229,6 +229,7 @@ export default class NeedsTeam extends React.Component {
+
);
diff --git a/webapp/components/reset_status_modal/index.js b/webapp/components/reset_status_modal/index.js
new file mode 100644
index 0000000000..34f08c7a56
--- /dev/null
+++ b/webapp/components/reset_status_modal/index.js
@@ -0,0 +1,34 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {connect} from 'react-redux';
+import {bindActionCreators} from 'redux';
+
+import {Preferences} from 'mattermost-redux/constants';
+import {get} from 'mattermost-redux/selectors/entities/preferences';
+
+import {savePreferences} from 'mattermost-redux/actions/preferences';
+import {setStatus} from 'mattermost-redux/actions/users';
+import {autoResetStatus} from 'actions/user_actions.jsx';
+
+import ResetStatusModal from './reset_status_modal.jsx';
+
+function mapStateToProps(state, ownProps) {
+ const {currentUserId} = state.entities.users;
+ return {
+ ...ownProps,
+ autoResetPref: get(state, Preferences.CATEGORY_AUTO_RESET_MANUAL_STATUS, currentUserId, '')
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ autoResetStatus,
+ setStatus,
+ savePreferences
+ }, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(ResetStatusModal);
diff --git a/webapp/components/reset_status_modal/reset_status_modal.jsx b/webapp/components/reset_status_modal/reset_status_modal.jsx
new file mode 100644
index 0000000000..4a04d75615
--- /dev/null
+++ b/webapp/components/reset_status_modal/reset_status_modal.jsx
@@ -0,0 +1,142 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import ConfirmModal from 'components/confirm_modal.jsx';
+
+import {toTitleCase} from 'utils/utils.jsx';
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import {FormattedMessage} from 'react-intl';
+import {Preferences} from 'mattermost-redux/constants';
+
+export default class ResetStatusModal extends React.PureComponent {
+ static propTypes = {
+
+ /*
+ * The user's preference for whether their status is automatically reset
+ */
+ autoResetPref: PropTypes.string,
+ actions: PropTypes.shape({
+
+ /*
+ * Function to get and then reset the user's status if needed
+ */
+ autoResetStatus: PropTypes.func.isRequired,
+
+ /*
+ * Function to set the status for a user
+ */
+ setStatus: PropTypes.func.isRequired,
+
+ /*
+ * Function to save user preferences
+ */
+ savePreferences: PropTypes.func.isRequired
+ }).isRequired
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ show: false,
+ currentUserStatus: {}
+ };
+ }
+
+ componentDidMount() {
+ this.props.actions.autoResetStatus().then(
+ (status) => {
+ const statusIsManual = status.manual;
+ const autoResetPrefNotSet = this.props.autoResetPref === '';
+
+ this.setState({
+ currentUserStatus: status, // Set in state until status refactor where we store 'manual' field in redux
+ show: Boolean(statusIsManual && autoResetPrefNotSet)
+ });
+ }
+ );
+ }
+
+ onConfirm = (checked) => {
+ this.setState({show: false});
+
+ const newStatus = {...this.state.currentUserStatus};
+ newStatus.status = 'online';
+ this.props.actions.setStatus(newStatus);
+
+ if (checked) {
+ const pref = {category: Preferences.CATEGORY_AUTO_RESET_MANUAL_STATUS, user_id: newStatus.user_id, name: newStatus.user_id, value: 'true'};
+ this.props.actions.savePreferences(pref.user_id, [pref]);
+ }
+ }
+
+ onCancel = (checked) => {
+ this.setState({show: false});
+
+ if (checked) {
+ const status = {...this.state.currentUserStatus};
+ const pref = {category: Preferences.CATEGORY_AUTO_RESET_MANUAL_STATUS, user_id: status.user_id, name: status.user_id, value: 'false'};
+ this.props.actions.savePreferences(pref.user_id, [pref]);
+ }
+ }
+
+ render() {
+ const userStatus = toTitleCase(this.state.currentUserStatus.status || '');
+ const manualStatusTitle = (
+
+ );
+
+ const manualStatusMessage = (
+
+ );
+
+ const manualStatusButton = (
+
+ );
+
+ const manualStatusCancel = (
+
+ );
+
+ const manualStatusCheckbox = (
+
+ );
+
+ return (
+
+ );
+ }
+}
diff --git a/webapp/components/team_members_dropdown/team_members_dropdown.jsx b/webapp/components/team_members_dropdown/team_members_dropdown.jsx
index 0837fabd08..041c4a8597 100644
--- a/webapp/components/team_members_dropdown/team_members_dropdown.jsx
+++ b/webapp/components/team_members_dropdown/team_members_dropdown.jsx
@@ -357,7 +357,7 @@ export default class TeamMembersDropdown extends React.Component {
show={this.state.showDemoteModal}
title={title}
message={message}
- confirmButton={confirmButton}
+ confirmButtonText={confirmButton}
onConfirm={this.handleDemoteSubmit}
onCancel={this.handleDemoteCancel}
/>
diff --git a/webapp/components/user_settings/user_settings_modal.jsx b/webapp/components/user_settings/user_settings_modal.jsx
index 0074ffce8b..eee41ac102 100644
--- a/webapp/components/user_settings/user_settings_modal.jsx
+++ b/webapp/components/user_settings/user_settings_modal.jsx
@@ -269,7 +269,7 @@ class UserSettingsModal extends React.Component {
+ }
+ checkboxText={
+
+ }
+ confirmButtonClass="btn btn-primary"
+ confirmButtonText={
+
+ }
+ message={
+
+ }
+ onCancel={[Function]}
+ onConfirm={[Function]}
+ show={false}
+ showCheckbox={true}
+ title={
+
+ }
+/>
+`;
diff --git a/webapp/tests/components/reset_status_modal.test.jsx b/webapp/tests/components/reset_status_modal.test.jsx
new file mode 100644
index 0000000000..420bb2bfe0
--- /dev/null
+++ b/webapp/tests/components/reset_status_modal.test.jsx
@@ -0,0 +1,29 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+import {shallow} from 'enzyme';
+
+import ResetStatusModal from 'components/reset_status_modal/reset_status_modal.jsx';
+
+describe('components/ResetStatusModal', () => {
+ test('should match snapshot', () => {
+ function emptyFunction() {} //eslint-disable-line no-empty-function
+
+ async function fakeAutoReset() { //eslint-disable-line require-await
+ return {status: 'away', manual: true, user_id: 'fake'};
+ }
+
+ const wrapper = shallow(
+
+ );
+ expect(wrapper).toMatchSnapshot();
+ });
+});
diff --git a/webapp/yarn.lock b/webapp/yarn.lock
index 343f1582f7..01cba4d16e 100644
--- a/webapp/yarn.lock
+++ b/webapp/yarn.lock
@@ -4884,7 +4884,7 @@ math-expression-evaluator@^1.2.14:
mattermost-redux@mattermost/mattermost-redux#webapp-master:
version "0.0.1"
- resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/eaf3d811a8f9b9814f4d07c49bfc6d91e73a38be"
+ resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/30af9bdf41aeac4ca5a37817773bd8a9b8372a4a"
dependencies:
deep-equal "1.0.1"
harmony-reflect "1.5.1"