Show warning message when user has been muted several times.

This commit is contained in:
picklesrus 2020-12-17 15:43:07 -05:00
parent 338ac99e6b
commit d8ad0c38ea
5 changed files with 85 additions and 6 deletions

View file

@ -20,6 +20,10 @@ class MuteModal extends React.Component {
'handleNext', 'handleNext',
'handlePrevious' 'handlePrevious'
]); ]);
this.numSteps = 2;
if (this.props.showWarning) {
this.numSteps++;
}
this.state = { this.state = {
step: 0 step: 0
}; };
@ -82,6 +86,23 @@ class MuteModal extends React.Component {
/> />
</p> </p>
</MuteStep> </MuteStep>
{this.props.showWarning ? (
<MuteStep
bottomImg="/svgs/commenting/warning.svg"
bottomImgClass="bottom-img"
header={this.props.intl.formatMessage({id: 'comments.muted.warningBlocked'})}
>
<p>
<FormattedMessage
id="comments.muted.warningCareful"
values={{CommunityGuidelinesLink: (
<a href="/community_guidelines">
<FormattedMessage id="report.CommunityGuidelinesLinkText" />
</a>
)}}
/>
</p>
</MuteStep>) : null}
</Progression> </Progression>
<FlexRow className={classNames('nav-divider')} /> <FlexRow className={classNames('nav-divider')} />
<FlexRow className={classNames('mute-nav')}> <FlexRow className={classNames('mute-nav')}>
@ -97,7 +118,7 @@ class MuteModal extends React.Component {
</div> </div>
</Button> </Button>
) : null } ) : null }
{this.state.step >= 1 ? ( {this.state.step >= this.numSteps - 1 ? (
<Button <Button
className={classNames('close-button')} className={classNames('close-button')}
onClick={this.props.onRequestClose} onClick={this.props.onRequestClose}
@ -131,6 +152,7 @@ MuteModal.propTypes = {
muteStepContent: PropTypes.string muteStepContent: PropTypes.string
}), }),
onRequestClose: PropTypes.func, onRequestClose: PropTypes.func,
showWarning: PropTypes.bool,
timeMuted: PropTypes.string timeMuted: PropTypes.string
}; };

View file

@ -354,6 +354,9 @@
"comments.muted.moreInfoGuidelines": "If you would like more information, you can read the {CommunityGuidelinesLink}.", "comments.muted.moreInfoGuidelines": "If you would like more information, you can read the {CommunityGuidelinesLink}.",
"comments.muted.moreInfoModal": "For more information, {clickHereLink}.", "comments.muted.moreInfoModal": "For more information, {clickHereLink}.",
"comments.muted.clickHereLinkText": "click here", "comments.muted.clickHereLinkText": "click here",
"comments.muted.warningBlocked": "If you continue to post comments like this, it will cause you to be blocked from using Scratch",
"comments.muted.warningCareful": "We don't want that to happen, so please be careful and make sure you have read and understand the {CommunityGuidelinesLink} before you try to post again!",
"social.embedLabel": "Embed", "social.embedLabel": "Embed",
"social.copyEmbedLinkText": "Copy embed", "social.copyEmbedLinkText": "Copy embed",

View file

@ -48,7 +48,8 @@ class ComposeComment extends React.Component {
error: null, error: null,
appealId: null, appealId: null,
muteOpen: false, muteOpen: false,
muteExpiresAtMs: this.props.muteStatus.muteExpiresAt * 1000 // convert to ms muteExpiresAtMs: this.props.muteStatus.muteExpiresAt * 1000, // convert to ms
showWarning: this.props.muteStatus.showWarning ? this.props.muteStatus.showWarning : false
}; };
} }
handleInput (event) { handleInput (event) {
@ -80,12 +81,14 @@ class ComposeComment extends React.Component {
let muteOpen = false; let muteOpen = false;
let muteExpiresAtMs = 0; let muteExpiresAtMs = 0;
let rejectedStatus = ComposeStatus.REJECTED; let rejectedStatus = ComposeStatus.REJECTED;
let showWarning = false;
if (body.status && body.status.mute_status) { if (body.status && body.status.mute_status) {
muteExpiresAtMs = body.status.mute_status.muteExpiresAt * 1000; // convert to ms muteExpiresAtMs = body.status.mute_status.muteExpiresAt * 1000; // convert to ms
rejectedStatus = ComposeStatus.REJECTED_MUTE; rejectedStatus = ComposeStatus.REJECTED_MUTE;
if (this.shouldShowMuteModal(body.status.mute_status.offenses)) { if (this.shouldShowMuteModal(body.status.mute_status.offenses)) {
muteOpen = true; muteOpen = true;
} }
showWarning = body.status.mute_status.showWarning;
} }
// Note: does not reset the message state // Note: does not reset the message state
this.setState({ this.setState({
@ -93,7 +96,8 @@ class ComposeComment extends React.Component {
error: body.rejected, error: body.rejected,
appealId: body.appealId, appealId: body.appealId,
muteOpen: muteOpen, muteOpen: muteOpen,
muteExpiresAtMs: muteExpiresAtMs muteExpiresAtMs: muteExpiresAtMs,
showWarning: showWarning
}); });
return; return;
} }
@ -287,6 +291,7 @@ class ComposeComment extends React.Component {
className="mod-mute" className="mod-mute"
muteModalMessages={this.getMuteMessageInfo()} muteModalMessages={this.getMuteMessageInfo()}
shouldCloseOnOverlayClick={false} shouldCloseOnOverlayClick={false}
showWarning={this.state.showWarning}
timeMuted={formatTime.formatRelativeTime(this.state.muteExpiresAtMs, window._locale)} timeMuted={formatTime.formatRelativeTime(this.state.muteExpiresAtMs, window._locale)}
onRequestClose={this.handleMuteClose} onRequestClose={this.handleMuteClose}
/> />
@ -300,7 +305,8 @@ ComposeComment.propTypes = {
commenteeId: PropTypes.number, commenteeId: PropTypes.number,
muteStatus: PropTypes.shape({ muteStatus: PropTypes.shape({
offenses: PropTypes.array, offenses: PropTypes.array,
muteExpiresAt: PropTypes.number muteExpiresAt: PropTypes.number,
showWarning: PropTypes.bool
}), }),
onAddComment: PropTypes.func, onAddComment: PropTypes.func,
onCancel: PropTypes.func, onCancel: PropTypes.func,
@ -317,7 +323,7 @@ ComposeComment.propTypes = {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
muteStatus: state.session.session.permissions.mute_status ? muteStatus: state.session.session.permissions.mute_status ?
state.session.session.permissions.mute_status : state.session.session.permissions.mute_status :
{muteExpiresAt: 0, offenses: []}, {muteExpiresAt: 0, offenses: [], showWarning: false},
user: state.session.session.user user: state.session.session.user
}); });

View file

@ -104,7 +104,8 @@ describe('Compose Comment test', () => {
permissions: { permissions: {
mute_status: { mute_status: {
muteExpiresAt: 5, muteExpiresAt: 5,
offenses: [] offenses: [],
showWarning: true
} }
} }
} }
@ -114,6 +115,7 @@ describe('Compose Comment test', () => {
const commentInstance = component.instance(); const commentInstance = component.instance();
// Check conversion to ms from seconds is done at init time. // Check conversion to ms from seconds is done at init time.
expect(commentInstance.state.muteExpiresAtMs).toEqual(5 * 1000); expect(commentInstance.state.muteExpiresAtMs).toEqual(5 * 1000);
expect(commentInstance.state.showWarning).toBe(true);
// Compose box should be hidden if muted unless they got muted due to a comment they just posted. // Compose box should be hidden if muted unless they got muted due to a comment they just posted.
expect(component.find('FlexRow.compose-comment').exists()).toEqual(false); expect(component.find('FlexRow.compose-comment').exists()).toEqual(false);
expect(component.find('MuteModal').exists()).toEqual(false); expect(component.find('MuteModal').exists()).toEqual(false);
@ -191,9 +193,41 @@ describe('Compose Comment test', () => {
commentInstance.setState({muteOpen: true}); commentInstance.setState({muteOpen: true});
component.update(); component.update();
expect(component.find('MuteModal').exists()).toEqual(true); expect(component.find('MuteModal').exists()).toEqual(true);
expect(component.find('MuteModal').props().showWarning).toBe(false);
global.Date.now = realDateNow; global.Date.now = realDateNow;
}); });
test('Mute Modal gets showWarning props from state', () => {
const store = mockStore({
session: {
session: {
user: {},
permissions: {
mute_status: {}
}
}
}
});
const component = mountWithIntl(
<ComposeComment
{...defaultProps()}
/>
, {context: {store}}
);
// set state on the ComposeComment component, not the wrapper
const commentInstance = component.find('ComposeComment').instance();
commentInstance.setState({muteOpen: true});
component.update();
expect(component.find('MuteModal').exists()).toEqual(true);
expect(component.find('MuteModal').props().showWarning).toBe(false);
commentInstance.setState({
muteOpen: true,
showWarning: true
});
component.update();
expect(component.find('MuteModal').props().showWarning).toBe(true);
});
test('shouldShowMuteModal is false when list is undefined ', () => { test('shouldShowMuteModal is false when list is undefined ', () => {
const commentInstance = getComposeCommentWrapper({}).instance(); const commentInstance = getComposeCommentWrapper({}).instance();
expect(commentInstance.shouldShowMuteModal()).toBe(false); expect(commentInstance.shouldShowMuteModal()).toBe(false);

View file

@ -33,6 +33,20 @@ describe('MuteModalTest', () => {
expect(component.find('button.back-button').exists()).toEqual(false); expect(component.find('button.back-button').exists()).toEqual(false);
}); });
test('Mute Modal shows extra showWarning step', () => {
const component = mountWithIntl(
<MuteModal
showWarning
muteModalMessages={defaultMessages}
/>
);
component.find('MuteModal').instance()
.setState({step: 2});
component.update();
expect(component.find('MuteStep').prop('bottomImg')).toEqual('/svgs/commenting/warning.svg');
expect(component.find('MuteStep').prop('totalSteps')).toEqual(3);
});
test('Mute Modal shows back & close button on last step', () => { test('Mute Modal shows back & close button on last step', () => {
const component = mountWithIntl( const component = mountWithIntl(
<MuteModal muteModalMessages={defaultMessages} /> <MuteModal muteModalMessages={defaultMessages} />