Commit 8e782cb0 authored by Joram Wilander's avatar Joram Wilander Committed by GitHub

PLT-4257 Add pop-up asking if user wants to reset status (#6526)

* Add pop-up asking if user wants to reset status

* Update test snapshot

* Update prop name for old uses of confirm modal

* Updating checkbox (#6586)

* Updating style for checkbox (#6596)
parent 426ae1da
......@@ -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;
};
}
......@@ -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}
/>
......
......@@ -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}
/>
......
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {FormattedMessage} from 'react-intl';
import React from 'react';
import {Modal} from 'react-bootstrap';
import PropTypes from 'prop-types';
import React from 'react';
import {FormattedMessage} from 'react-intl';
export default class ConfirmModal extends React.Component {
constructor(props) {
super(props);
static propTypes = {
/*
* 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,
this.handleKeypress = this.handleKeypress.bind(this);
/*
* 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 = (
<div className='checkbox text-right margin-bottom--none'>
<label>
<input
ref='checkbox'
type='checkbox'
/>
{this.props.checkboxText}
</label>
</div>
);
}
let cancelText;
if (this.props.cancelButtonText) {
cancelText = this.props.cancelButtonText;
} else {
cancelText = (
<FormattedMessage
id='confirm_modal.cancel'
defaultMessage='Cancel'
/>
);
}
return (
<Modal
className='modal-confirm'
......@@ -51,43 +140,25 @@ export default class ConfirmModal extends React.Component {
</Modal.Header>
<Modal.Body>
{this.props.message}
{checkbox}
</Modal.Body>
<Modal.Footer>
<button
type='button'
className='btn btn-default'
onClick={this.props.onCancel}
onClick={this.handleCancel}
>
<FormattedMessage
id='confirm_modal.cancel'
defaultMessage='Cancel'
/>
{cancelText}
</button>
<button
type='button'
className={this.props.confirmButtonClass}
onClick={this.props.onConfirm}
onClick={this.handleConfirm}
>
{this.props.confirmButton}
{this.props.confirmButtonText}
</button>
</Modal.Footer>
</Modal>
);
}
}
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
};
......@@ -120,7 +120,9 @@ export default class CreatePost extends React.Component {
}
doSubmit(e) {
e.preventDefault();
if (e) {
e.preventDefault();
}
const post = {};
post.file_ids = [];
......
......@@ -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}
......
......@@ -719,7 +719,7 @@ export default class EditCommand extends React.Component {
<ConfirmModal
title={confirmTitle}
message={confirmMessage}
confirmButton={confirmButton}
confirmButtonText={confirmButton}
show={this.state.showConfirmModal}
onConfirm={this.handleUpdate}
onCancel={this.confirmModalDismissed}
......
......@@ -180,7 +180,7 @@ export default class EditOutgoingWebhook extends AbstractOutgoingWebhook {
<ConfirmModal
title={confirmTitle}
message={confirmMessage}
confirmButton={confirmButton}
confirmButtonText={confirmButton}
show={this.state.showConfirmModal}
onConfirm={this.handleUpdate}
onCancel={this.confirmModalDismissed}
......
......@@ -517,7 +517,7 @@ class InviteMemberModal extends React.Component {
<ConfirmModal
title={formatMessage(holders.modalTitle)}
message={formatMessage(holders.modalMessage)}
confirmButton={formatMessage(holders.modalButton)}
confirmButtonText={formatMessage(holders.modalButton)}
show={this.state.showConfirmModal}
onConfirm={this.handleHide.bind(this, false)}
onCancel={() => this.setState({showConfirmModal: false})}
......
......@@ -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}
/>
......
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 {
<EditPostModal/>
<DeletePostModal/>
<RemovedFromChannelModal/>
<ResetStatusModal/>
</div>
</div>
);
......
// 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);
// 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 = (
<FormattedMessage
id='modal.manual_status.title'
defaultMessage='Your status is set to "{status}"'
values={{
status: userStatus
}}
/>
);
const manualStatusMessage = (
<FormattedMessage
id='modal.manual_status.message'
defaultMessage='Would you like to switch your status to "Online"?'
/>
);
const manualStatusButton = (
<FormattedMessage
id='modal.manual_status.button'
defaultMessage='Yes, set my status to "Online"'
/>
);
const manualStatusCancel = (
<FormattedMessage
id='modal.manual_status.cancel'
defaultMessage='No, keep it as "{status}"'
values={{
status: userStatus
}}
/>
);
const manualStatusCheckbox = (
<FormattedMessage
id='modal.manual_status.ask'
defaultMessage='Do not ask me again'
/>
);
return (
<ConfirmModal
show={this.state.show}
title={manualStatusTitle}
message={manualStatusMessage}
confirmButtonText={manualStatusButton}
onConfirm={this.onConfirm}
cancelButtonText={manualStatusCancel}
onCancel={this.onCancel}
showCheckbox={true}
checkboxText={manualStatusCheckbox}
/>
);
}
}
......@@ -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}
/>
......
......@@ -269,7 +269,7 @@ class UserSettingsModal extends React.Component {
<ConfirmModal
title={formatMessage(holders.confirmTitle)}
message={formatMessage(holders.confirmMsg)}
confirmButton={formatMessage(holders.confirmBtns)}
confirmButtonText={formatMessage(holders.confirmBtns)}
show={this.state.showConfirmModal}
onConfirm={this.handleConfirm}
onCancel={this.handleCancelConfirmation}
......
......@@ -1791,6 +1791,11 @@
"more_direct_channels.new_convo_note": "This will start a new conversation. If you’re adding a lot of people, consider creating a private channel instead.",
"more_direct_channels.new_convo_note.full": "You’ve reached the maximum number of people for this conversation. Consider creating a private channel instead.",
"more_direct_channels.title": "Direct Messages",
"modal.manaul_status.title": "Your status is set to \"{status}\"",
"modal.manaul_status.message": "Would you like to switch your status to \"Online\"?",
"modal.manaul_status.button": "Yes, set my status to \"Online\"",
"modal.manaul_status.cancel": "No, keep it as \"{status}\"",
"modal.manaul_status.ask": "Do not ask me again",
"msg_typing.areTyping": "{users} and {last} are typing...",
"msg_typing.isTyping": "{user} is typing...",
"msg_typing.someone": "Someone",
......
@charset 'UTF-8';
.radio,
.checkbox {
&.margin-bottom--none {
margin-bottom: 0;
}
}
.form-horizontal {
.modal-intro {
margin: -10px 0 30px;
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/ResetStatusModal should match snapshot 1`] = `
<ConfirmModal
cancelButtonText={
<FormattedMessage
defaultMessage="No, keep it as \\"{status}\\""
id="modal.manual_status.cancel"
values={
Object {
"status": "",
}
}
/>
}
checkboxText={
<FormattedMessage
defaultMessage="Do not ask me again"
id="modal.manual_status.ask"
values={Object {}}
/>
}
confirmButtonClass="btn btn-primary"
confirmButtonText={
<FormattedMessage
defaultMessage="Yes, set my status to \\"Online\\""
id="modal.manual_status.button"
values={Object {}}
/>
}
message={
<FormattedMessage
defaultMessage="Would you like to switch your status to \\"Online\\"?"
id="modal.manual_status.message"
values={Object {}}
/>
}
onCancel={[Function]}
onConfirm={[Function]}
show={false}
showCheckbox={true}
title={
<FormattedMessage
defaultMessage="Your status is set to \\"{status}\\""
id="modal.manual_status.title"
values={
Object {
"status": "",
}
}
/>
}
/>
`;
// 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(
<ResetStatusModal
autoResetPref=''
actions={{
autoResetStatus: fakeAutoReset,
setStatus: emptyFunction,
savePreferences: emptyFunction
}}
/>
);
expect(wrapper).toMatchSnapshot();
});
});
......@@ -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"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment