Skip to content
Extraits de code Groupes Projets
Valider fd3d45d3 rédigé par ThibG's avatar ThibG Validation de Eugen Rochko
Parcourir les fichiers

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
parent 2b853158
Aucune branche associée trouvée
Aucune étiquette associée trouvée
Aucune requête de fusion associée trouvée
...@@ -23,9 +23,14 @@ class ActionBar extends React.PureComponent { ...@@ -23,9 +23,14 @@ class ActionBar extends React.PureComponent {
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map.isRequired, account: ImmutablePropTypes.map.isRequired,
onLogout: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
}; };
handleLogout = () => {
this.props.onLogout();
}
render () { render () {
const { intl } = this.props; const { intl } = this.props;
...@@ -44,7 +49,7 @@ class ActionBar extends React.PureComponent { ...@@ -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.domain_blocks), to: '/domain_blocks' });
menu.push({ text: intl.formatMessage(messages.filters), href: '/filters' }); menu.push({ text: intl.formatMessage(messages.filters), href: '/filters' });
menu.push(null); 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 ( return (
<div className='compose__action-bar'> <div className='compose__action-bar'>
......
...@@ -12,6 +12,7 @@ export default class NavigationBar extends ImmutablePureComponent { ...@@ -12,6 +12,7 @@ export default class NavigationBar extends ImmutablePureComponent {
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map.isRequired, account: ImmutablePropTypes.map.isRequired,
onLogout: PropTypes.func.isRequired,
onClose: PropTypes.func, onClose: PropTypes.func,
}; };
...@@ -33,7 +34,7 @@ export default class NavigationBar extends ImmutablePureComponent { ...@@ -33,7 +34,7 @@ export default class NavigationBar extends ImmutablePureComponent {
<div className='navigation-bar__actions'> <div className='navigation-bar__actions'>
<IconButton className='close' title='' icon='close' onClick={this.props.onClose} /> <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>
</div> </div>
); );
......
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { defineMessages, injectIntl } from 'react-intl';
import NavigationBar from '../components/navigation_bar'; import NavigationBar from '../components/navigation_bar';
import { logOut } from 'mastodon/utils/log_out';
import { openModal } from 'mastodon/actions/modal';
import { me } from '../../../initial_state'; 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 => { const mapStateToProps = state => {
return { return {
account: state.getIn(['accounts', me]), 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));
...@@ -12,9 +12,11 @@ import Motion from '../ui/util/optional_motion'; ...@@ -12,9 +12,11 @@ import Motion from '../ui/util/optional_motion';
import spring from 'react-motion/lib/spring'; import spring from 'react-motion/lib/spring';
import SearchResultsContainer from './containers/search_results_container'; import SearchResultsContainer from './containers/search_results_container';
import { changeComposing } from '../../actions/compose'; import { changeComposing } from '../../actions/compose';
import { openModal } from 'mastodon/actions/modal';
import elephantUIPlane from '../../../images/elephant_ui_plane.svg'; import elephantUIPlane from '../../../images/elephant_ui_plane.svg';
import { mascot } from '../../initial_state'; import { mascot } from '../../initial_state';
import Icon from 'mastodon/components/icon'; import Icon from 'mastodon/components/icon';
import { logOut } from 'mastodon/utils/log_out';
const messages = defineMessages({ const messages = defineMessages({
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
...@@ -25,6 +27,8 @@ const messages = defineMessages({ ...@@ -25,6 +27,8 @@ const messages = defineMessages({
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' }, 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) => ({ const mapStateToProps = (state, ownProps) => ({
...@@ -61,6 +65,21 @@ class Compose extends React.PureComponent { ...@@ -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 = () => { onFocus = () => {
this.props.dispatch(changeComposing(true)); this.props.dispatch(changeComposing(true));
} }
...@@ -92,7 +111,7 @@ class Compose extends React.PureComponent { ...@@ -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> <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='/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> </nav>
); );
} }
......
import { connect } from 'react-redux';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { invitesEnabled, version, repository, source_url } from 'mastodon/initial_state'; 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 }) => ( const messages = defineMessages({
<div className='getting-started__footer'> logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
<ul> logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' },
{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> const mapDispatchToProps = (dispatch, { intl }) => ({
<li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li> onLogout () {
<li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li> dispatch(openModal('CONFIRM', {
<li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li> message: intl.formatMessage(messages.logoutMessage),
<li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li> confirm: intl.formatMessage(messages.logoutConfirm),
<li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li> onConfirm: () => logOut(),
<li><a href='/auth/sign_out' data-method='delete'><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a></li> }));
</ul> },
});
<p>
<FormattedMessage export default @injectIntl
id='getting_started.open_source_notice' @connect(null, mapDispatchToProps)
defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.' class LinkFooter extends React.PureComponent {
values={{ github: <span><a href={source_url} rel='noopener' target='_blank'>{repository}</a> (v{version})</span> }}
/> static propTypes = {
</p> withHotkeys: PropTypes.bool,
</div> onLogout: PropTypes.func.isRequired,
); intl: PropTypes.object.isRequired,
};
LinkFooter.propTypes = {
withHotkeys: PropTypes.bool, 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>
);
}
};
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();
};
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Terminez d'abord l'édition de ce message.
Veuillez vous inscrire ou vous pour commenter