diff --git a/client/modules/IDE/components/ErrorModal.jsx b/client/modules/IDE/components/ErrorModal.jsx index 099065a549..a1d0f8abc2 100644 --- a/client/modules/IDE/components/ErrorModal.jsx +++ b/client/modules/IDE/components/ErrorModal.jsx @@ -15,6 +15,19 @@ class ErrorModal extends React.Component { ); } + oauthError() { + const { t, service } = this.props; + const serviceLabels = { + github: 'GitHub', + google: 'Google' + }; + return ( +

+ {t('ErrorModal.LinkMessage', { serviceauth: serviceLabels[service] })} +

+ ); + } + staleSession() { return (

@@ -42,6 +55,8 @@ class ErrorModal extends React.Component { return this.staleSession(); } else if (this.props.type === 'staleProject') { return this.staleProject(); + } else if (this.props.type === 'oauthError') { + return this.oauthError(); } })()} @@ -52,7 +67,12 @@ class ErrorModal extends React.Component { ErrorModal.propTypes = { type: PropTypes.string.isRequired, closeModal: PropTypes.func.isRequired, - t: PropTypes.func.isRequired + t: PropTypes.func.isRequired, + service: PropTypes.string +}; + +ErrorModal.defaultProps = { + service: '' }; export default withTranslation()(ErrorModal); diff --git a/client/modules/User/actions.js b/client/modules/User/actions.js index a5b22edbbd..0ce673acae 100644 --- a/client/modules/User/actions.js +++ b/client/modules/User/actions.js @@ -270,3 +270,20 @@ export function removeApiKey(keyId) { Promise.reject(new Error(response.data.error)); }); } + +export function unlinkService(service) { + return (dispatch) => { + if (!['github', 'google'].includes(service)) return; + apiClient.delete(`/auth/${service}`) + .then((response) => { + dispatch({ + type: ActionTypes.AUTH_USER, + user: response.data + }); + }).catch((error) => { + const { response } = error; + const message = response.message || response.data.error; + dispatch(authError(message)); + }); + }; +} diff --git a/client/modules/User/components/AccountForm.jsx b/client/modules/User/components/AccountForm.jsx index c1bfe6dbfc..9682171b44 100644 --- a/client/modules/User/components/AccountForm.jsx +++ b/client/modules/User/components/AccountForm.jsx @@ -115,7 +115,7 @@ AccountForm.propTypes = { newPassword: PropTypes.object.isRequired, // eslint-disable-line }).isRequired, user: PropTypes.shape({ - verified: PropTypes.number.isRequired, + verified: PropTypes.string.isRequired, emailVerificationInitiate: PropTypes.bool.isRequired, }).isRequired, handleSubmit: PropTypes.func.isRequired, diff --git a/client/modules/User/components/SocialAuthButton.jsx b/client/modules/User/components/SocialAuthButton.jsx index ed79dfd2ad..94527b5ac7 100644 --- a/client/modules/User/components/SocialAuthButton.jsx +++ b/client/modules/User/components/SocialAuthButton.jsx @@ -2,11 +2,12 @@ import PropTypes from 'prop-types'; import React from 'react'; import styled from 'styled-components'; import { withTranslation } from 'react-i18next'; +import { useDispatch } from 'react-redux'; import { remSize } from '../../../theme'; - import { GithubIcon, GoogleIcon } from '../../../common/icons'; import Button from '../../../common/Button'; +import { unlinkService } from '../actions'; const authUrls = { github: '/auth/github', @@ -23,22 +24,51 @@ const services = { google: 'google' }; +const servicesLabels = { + github: 'GitHub', + google: 'Google' +}; + const StyledButton = styled(Button)` width: ${remSize(300)}; `; -function SocialAuthButton({ service, t }) { +function SocialAuthButton({ + service, linkStyle, isConnected, t +}) { const ServiceIcon = icons[service]; - const labels = { - github: t('SocialAuthButton.Github'), - google: t('SocialAuthButton.Google') - }; + const serviceLabel = servicesLabels[service]; + const loginLabel = t('SocialAuthButton.Login', { serviceauth: serviceLabel }); + const connectLabel = t('SocialAuthButton.Connect', { serviceauth: serviceLabel }); + const unlinkLabel = t('SocialAuthButton.Unlink', { serviceauth: serviceLabel }); + const ariaLabel = t('SocialAuthButton.LogoARIA', { serviceauth: service }); + const dispatch = useDispatch(); + if (linkStyle) { + if (isConnected) { + return ( + } + onClick={() => { dispatch(unlinkService(service)); }} + > + {unlinkLabel} + + ); + } + return ( + } + href={authUrls[service]} + > + {connectLabel} + + ); + } return ( } + iconBefore={} href={authUrls[service]} > - {labels[service]} + {loginLabel} ); } @@ -47,9 +77,16 @@ SocialAuthButton.services = services; SocialAuthButton.propTypes = { service: PropTypes.oneOf(['github', 'google']).isRequired, + linkStyle: PropTypes.bool, + isConnected: PropTypes.bool, t: PropTypes.func.isRequired }; +SocialAuthButton.defaultProps = { + linkStyle: false, + isConnected: false +}; + const SocialAuthButtonPublic = withTranslation()(SocialAuthButton); SocialAuthButtonPublic.services = services; export default SocialAuthButtonPublic; diff --git a/client/modules/User/pages/AccountView.jsx b/client/modules/User/pages/AccountView.jsx index f9f46cc8f6..fa0bd75a15 100644 --- a/client/modules/User/pages/AccountView.jsx +++ b/client/modules/User/pages/AccountView.jsx @@ -5,6 +5,8 @@ import { bindActionCreators } from 'redux'; import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; import { Helmet } from 'react-helmet'; import { withTranslation } from 'react-i18next'; +import { withRouter, browserHistory } from 'react-router'; +import { parse } from 'query-string'; import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions'; import AccountForm from '../components/AccountForm'; import apiClient from '../../../utils/apiClient'; @@ -12,8 +14,11 @@ import { validateSettings } from '../../../utils/reduxFormUtils'; import SocialAuthButton from '../components/SocialAuthButton'; import APIKeyForm from '../components/APIKeyForm'; import Nav from '../../../components/Nav'; +import ErrorModal from '../../IDE/components/ErrorModal'; +import Overlay from '../../App/components/Overlay'; function SocialLoginPanel(props) { + const { user } = props; return ( @@ -24,19 +29,37 @@ function SocialLoginPanel(props) { {props.t('AccountView.SocialLoginDescription')}

- - + +
); } +SocialLoginPanel.propTypes = { + user: PropTypes.shape({ + github: PropTypes.string, + google: PropTypes.string + }).isRequired +}; + class AccountView extends React.Component { componentDidMount() { document.body.className = this.props.theme; } render() { + const queryParams = parse(this.props.location.search); + const showError = !!queryParams.error; + const errorType = queryParams.error; const accessTokensUIEnabled = window.process.env.UI_ACCESS_TOKEN_ENABLED; return ( @@ -47,6 +70,21 @@ class AccountView extends React.Component {