* [WiP] Add a confirmation modal before logging out from the web interface * Add confirmation to logout link in getting started footer * Please CodeClimatemaster^2
@@ -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'> | |||
@@ -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> | |||
); | |||
@@ -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)); |
@@ -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> | |||
); | |||
} | |||
@@ -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> | |||
); | |||
} | |||
}; |
@@ -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(); | |||
}; |