Browse Source

Add confirmation modal when logging out from the web app (#11671)

* [WiP] Add a confirmation modal before logging out from the web interface

* Add confirmation to logout link in getting started footer

* Please CodeClimate
master^2
ThibG 4 years ago
committed by Eugen Rochko
parent
commit
fd3d45d38f
6 changed files with 146 additions and 33 deletions
  1. +6
    -1
      app/javascript/mastodon/features/compose/components/action_bar.js
  2. +2
    -1
      app/javascript/mastodon/features/compose/components/navigation_bar.js
  3. +19
    -1
      app/javascript/mastodon/features/compose/containers/navigation_container.js
  4. +20
    -1
      app/javascript/mastodon/features/compose/index.js
  5. +66
    -29
      app/javascript/mastodon/features/ui/components/link_footer.js
  6. +33
    -0
      app/javascript/mastodon/utils/log_out.js

+ 6
- 1
app/javascript/mastodon/features/compose/components/action_bar.js View File

@@ -23,9 +23,14 @@ class ActionBar extends React.PureComponent {

static propTypes = {
account: ImmutablePropTypes.map.isRequired,
onLogout: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};

handleLogout = () => {
this.props.onLogout();
}

render () {
const { intl } = this.props;

@@ -44,7 +49,7 @@ class ActionBar extends React.PureComponent {
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
menu.push({ text: intl.formatMessage(messages.filters), href: '/filters' });
menu.push(null);
menu.push({ text: intl.formatMessage(messages.logout), href: '/auth/sign_out', target: null, method: 'delete' });
menu.push({ text: intl.formatMessage(messages.logout), action: this.handleLogout });

return (
<div className='compose__action-bar'>


+ 2
- 1
app/javascript/mastodon/features/compose/components/navigation_bar.js View File

@@ -12,6 +12,7 @@ export default class NavigationBar extends ImmutablePureComponent {

static propTypes = {
account: ImmutablePropTypes.map.isRequired,
onLogout: PropTypes.func.isRequired,
onClose: PropTypes.func,
};

@@ -33,7 +34,7 @@ export default class NavigationBar extends ImmutablePureComponent {

<div className='navigation-bar__actions'>
<IconButton className='close' title='' icon='close' onClick={this.props.onClose} />
<ActionBar account={this.props.account} />
<ActionBar account={this.props.account} onLogout={this.props.onLogout} />
</div>
</div>
);


+ 19
- 1
app/javascript/mastodon/features/compose/containers/navigation_container.js View File

@@ -1,11 +1,29 @@
import { connect } from 'react-redux';
import { defineMessages, injectIntl } from 'react-intl';
import NavigationBar from '../components/navigation_bar';
import { logOut } from 'mastodon/utils/log_out';
import { openModal } from 'mastodon/actions/modal';
import { me } from '../../../initial_state';

const messages = defineMessages({
logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' },
});

const mapStateToProps = state => {
return {
account: state.getIn(['accounts', me]),
};
};

export default connect(mapStateToProps)(NavigationBar);
const mapDispatchToProps = (dispatch, { intl }) => ({
onLogout () {
dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.logoutMessage),
confirm: intl.formatMessage(messages.logoutConfirm),
onConfirm: () => logOut(),
}));
},
});

export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(NavigationBar));

+ 20
- 1
app/javascript/mastodon/features/compose/index.js View File

@@ -12,9 +12,11 @@ import Motion from '../ui/util/optional_motion';
import spring from 'react-motion/lib/spring';
import SearchResultsContainer from './containers/search_results_container';
import { changeComposing } from '../../actions/compose';
import { openModal } from 'mastodon/actions/modal';
import elephantUIPlane from '../../../images/elephant_ui_plane.svg';
import { mascot } from '../../initial_state';
import Icon from 'mastodon/components/icon';
import { logOut } from 'mastodon/utils/log_out';

const messages = defineMessages({
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
@@ -25,6 +27,8 @@ const messages = defineMessages({
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' },
logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' },
});

const mapStateToProps = (state, ownProps) => ({
@@ -61,6 +65,21 @@ class Compose extends React.PureComponent {
}
}

handleLogoutClick = e => {
const { dispatch, intl } = this.props;

e.preventDefault();
e.stopPropagation();

dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.logoutMessage),
confirm: intl.formatMessage(messages.logoutConfirm),
onConfirm: () => logOut(),
}));

return false;
}

onFocus = () => {
this.props.dispatch(changeComposing(true));
}
@@ -92,7 +111,7 @@ class Compose extends React.PureComponent {
<Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link>
)}
<a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><Icon id='cog' fixedWidth /></a>
<a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)}><Icon id='sign-out' fixedWidth /></a>
<a href='/auth/sign_out' className='drawer__tab' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)} onClick={this.handleLogoutClick}><Icon id='sign-out' fixedWidth /></a>
</nav>
);
}


+ 66
- 29
app/javascript/mastodon/features/ui/components/link_footer.js View File

@@ -1,35 +1,72 @@
import { connect } from 'react-redux';
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import { invitesEnabled, version, repository, source_url } from 'mastodon/initial_state';
import { logOut } from 'mastodon/utils/log_out';
import { openModal } from 'mastodon/actions/modal';

const LinkFooter = ({ withHotkeys }) => (
<div className='getting-started__footer'>
<ul>
{invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
{withHotkeys && <li><Link to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link> · </li>}
<li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>
<li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>
<li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
<li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
<li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li>
<li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li>
<li><a href='/auth/sign_out' data-method='delete'><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a></li>
</ul>

<p>
<FormattedMessage
id='getting_started.open_source_notice'
defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.'
values={{ github: <span><a href={source_url} rel='noopener' target='_blank'>{repository}</a> (v{version})</span> }}
/>
</p>
</div>
);

LinkFooter.propTypes = {
withHotkeys: PropTypes.bool,
};
const messages = defineMessages({
logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' },
});

const mapDispatchToProps = (dispatch, { intl }) => ({
onLogout () {
dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.logoutMessage),
confirm: intl.formatMessage(messages.logoutConfirm),
onConfirm: () => logOut(),
}));
},
});

export default @injectIntl
@connect(null, mapDispatchToProps)
class LinkFooter extends React.PureComponent {

static propTypes = {
withHotkeys: PropTypes.bool,
onLogout: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};

handleLogoutClick = e => {
e.preventDefault();
e.stopPropagation();

this.props.onLogout();

export default LinkFooter;
return false;
}

render () {
const { withHotkeys } = this.props;

return (
<div className='getting-started__footer'>
<ul>
{invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
{withHotkeys && <li><Link to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link> · </li>}
<li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>
<li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>
<li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
<li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
<li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li>
<li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li>
<li><a href='/auth/sign_out' onClick={this.handleLogoutClick}><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a></li>
</ul>

<p>
<FormattedMessage
id='getting_started.open_source_notice'
defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.'
values={{ github: <span><a href={source_url} rel='noopener' target='_blank'>{repository}</a> (v{version})</span> }}
/>
</p>
</div>
);
}

};

+ 33
- 0
app/javascript/mastodon/utils/log_out.js View File

@@ -0,0 +1,33 @@
import Rails from 'rails-ujs';

export const logOut = () => {
const form = document.createElement('form');

const methodInput = document.createElement('input');
methodInput.setAttribute('name', '_method');
methodInput.setAttribute('value', 'delete');
methodInput.setAttribute('type', 'hidden');
form.appendChild(methodInput);

const csrfToken = Rails.csrfToken();
const csrfParam = Rails.csrfParam();

if (csrfParam && csrfToken) {
const csrfInput = document.createElement('input');
csrfInput.setAttribute('name', csrfParam);
csrfInput.setAttribute('value', csrfToken);
csrfInput.setAttribute('type', 'hidden');
form.appendChild(csrfInput);
}

const submitButton = document.createElement('input');
submitButton.setAttribute('type', 'submit');
form.appendChild(submitButton);

form.method = 'post';
form.action = '/auth/sign_out';
form.style.display = 'none';

document.body.appendChild(form);
submitButton.click();
};

Loading…
Cancel
Save