diff --git a/client/coral-admin/src/actions/config.js b/client/coral-admin/src/actions/config.js
deleted file mode 100644
index 5e2995e85..000000000
--- a/client/coral-admin/src/actions/config.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export const CONFIG_UPDATED = 'CONFIG_UPDATED';
-
-export const fetchConfig = () => dispatch => {
- let json = document.getElementById('data');
- let data = JSON.parse(json.textContent);
- dispatch({ type: CONFIG_UPDATED, data });
-};
diff --git a/client/coral-admin/src/components/SignIn.js b/client/coral-admin/src/components/SignIn.js
index 71f14d12a..85bfb9810 100644
--- a/client/coral-admin/src/components/SignIn.js
+++ b/client/coral-admin/src/components/SignIn.js
@@ -2,11 +2,10 @@ import React from 'react';
import PropTypes from 'prop-types';
import styles from './SignIn.css';
import { Button, TextField, Alert } from 'coral-ui';
+import Recaptcha from 'coral-framework/components/Recaptcha';
class SignIn extends React.Component {
- constructor(props) {
- super(props);
- }
+ recaptcha = null;
handleForgotPasswordLink = e => {
e.preventDefault();
@@ -18,10 +17,23 @@ class SignIn extends React.Component {
handleSubmit = e => {
e.preventDefault();
this.props.onSubmit();
+
+ // Reset recaptcha because each response can only
+ // be used once.
+ if (this.recaptcha) {
+ this.recaptcha.reset();
+ }
+ };
+
+ handleRecaptchaRef = ref => {
+ this.recaptcha = ref;
+ setTimeout(() => {
+ console.log(ref);
+ }, 1000)
};
render() {
- const { email, password, errorMessage } = this.props;
+ const { email, password, errorMessage, requireRecaptcha } = this.props;
return (
);
}
@@ -68,6 +86,7 @@ SignIn.propTypes = {
onEmailChange: PropTypes.func.isRequired,
onPasswordChange: PropTypes.func.isRequired,
onForgotPasswordLink: PropTypes.func.isRequired,
+ onRecaptchaVerify: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
errorMessage: PropTypes.string.isRequired,
requireRecaptcha: PropTypes.bool.isRequired,
diff --git a/client/coral-admin/src/containers/Layout.js b/client/coral-admin/src/containers/Layout.js
index ea98000b8..484467936 100644
--- a/client/coral-admin/src/containers/Layout.js
+++ b/client/coral-admin/src/containers/Layout.js
@@ -2,7 +2,6 @@ import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Layout from '../components/Layout';
-import { fetchConfig } from '../actions/config';
import Login from '../containers/Login';
import { FullLoading } from '../components/FullLoading';
import BanUserDialog from './BanUserDialog';
@@ -14,12 +13,6 @@ import UserDetail from 'coral-admin/src/containers/UserDetail';
import PropTypes from 'prop-types';
class LayoutContainer extends React.Component {
- componentWillMount() {
- const { fetchConfig } = this.props;
-
- fetchConfig();
- }
-
render() {
const {
currentUser,
@@ -72,8 +65,6 @@ LayoutContainer.propTypes = {
checkedInitialLogin: PropTypes.bool,
logout: PropTypes.func,
toggleShortcutModal: PropTypes.func,
- TALK_RECAPTCHA_PUBLIC: PropTypes.string,
- fetchConfig: PropTypes.func,
};
const mapStateToProps = state => ({
@@ -84,7 +75,6 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
- fetchConfig,
toggleShortcutModal,
logout,
},
diff --git a/client/coral-admin/src/containers/SignIn.js b/client/coral-admin/src/containers/SignIn.js
index ab7886c3a..02dc8ba01 100644
--- a/client/coral-admin/src/containers/SignIn.js
+++ b/client/coral-admin/src/containers/SignIn.js
@@ -8,10 +8,15 @@ class SignInContainer extends Component {
state = {
email: '',
password: '',
+ recaptchaResponse: '',
};
handleSubmit = () => {
- this.props.signIn(this.state.email, this.state.password);
+ this.props.signIn(
+ this.state.email,
+ this.state.password,
+ this.state.recaptchaResponse
+ );
};
handleEmailChange = email => {
@@ -22,6 +27,10 @@ class SignInContainer extends Component {
this.setState({ password });
};
+ handleRecaptchaVerify = recaptchaResponse => {
+ this.setState({ recaptchaResponse });
+ };
+
render() {
return (
);
diff --git a/client/coral-admin/src/reducers/config.js b/client/coral-admin/src/reducers/config.js
deleted file mode 100644
index 0588f0a13..000000000
--- a/client/coral-admin/src/reducers/config.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import * as actions from '../actions/config';
-
-const initialState = {
- data: {},
-};
-
-export default function config(state = initialState, action) {
- switch (action.type) {
- case actions.CONFIG_UPDATED:
- return {
- ...state,
- data: action.data,
- };
- default:
- return state;
- }
-}
diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js
index 11801a379..9175698cf 100644
--- a/client/coral-admin/src/reducers/index.js
+++ b/client/coral-admin/src/reducers/index.js
@@ -4,7 +4,6 @@ import configure from './configure';
import community from './community';
import moderation from './moderation';
import install from './install';
-import config from './config';
import banUserDialog from './banUserDialog';
import suspendUserDialog from './suspendUserDialog';
import userDetail from './userDetail';
@@ -20,6 +19,5 @@ export default {
community,
moderation,
install,
- config,
ui,
};
diff --git a/client/coral-framework/components/Recaptcha.js b/client/coral-framework/components/Recaptcha.js
new file mode 100644
index 000000000..f0825df30
--- /dev/null
+++ b/client/coral-framework/components/Recaptcha.js
@@ -0,0 +1,52 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ReactRecaptcha from 'react-recaptcha';
+
+class Recaptcha extends React.Component {
+ static contextTypes = {
+ store: PropTypes.object,
+ };
+
+ ref = null;
+
+ handleRef = ref => {
+ this.ref = ref;
+ };
+
+ reset = () => this.ref.reset();
+
+ getSiteKey() {
+ // This should be fine because it's static and will never change.
+ // Prefer this to connect HOC because wie expose the instance method
+ // `reset`
+ return this.context.store.getState().static.TALK_RECAPTCHA_PUBLIC;
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+Recaptcha.defaultProps = {
+ render: 'explicit',
+ theme: 'dark',
+};
+
+Recaptcha.propTypes = {
+ onLoad: PropTypes.func,
+ onVerify: PropTypes.func.isRequired,
+ theme: PropTypes.string,
+ render: PropTypes.string,
+ sitekey: PropTypes.string.isRequired,
+};
+
+export default Recaptcha;
diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js
index af524b21d..fe63b4f30 100644
--- a/client/coral-framework/components/Slot.js
+++ b/client/coral-framework/components/Slot.js
@@ -5,6 +5,7 @@ import { connect } from 'react-redux';
import kebabCase from 'lodash/kebabCase';
import PropTypes from 'prop-types';
import isEqual from 'lodash/isEqual';
+import get from 'lodash/get';
import { getShallowChanges } from 'coral-framework/utils';
const emptyConfig = {};
@@ -68,7 +69,7 @@ class Slot extends React.Component {
} = this.props;
const { plugins } = this.context;
let children = this.getChildren();
- const pluginConfig = reduxState.config.pluginConfig || emptyConfig;
+ const pluginConfig = get(reduxState, 'config.pluginConfig') || emptyConfig;
if (children.length === 0 && DefaultComponent) {
const props = plugins.getSlotComponentProps(
DefaultComponent,
diff --git a/client/coral-framework/hocs/withSignIn.js b/client/coral-framework/hocs/withSignIn.js
index a725f01e9..02c631068 100644
--- a/client/coral-framework/hocs/withSignIn.js
+++ b/client/coral-framework/hocs/withSignIn.js
@@ -18,6 +18,7 @@ export default hoistStatics(WrappedComponent => {
state = {
error: null,
loading: false,
+ requireRecaptcha: false,
};
signIn = (email, password, recaptchaResponse) => {
@@ -45,7 +46,12 @@ export default hoistStatics(WrappedComponent => {
if (!error.status || error.status !== 401) {
console.error(error);
}
- this.setState({ loading: false, error });
+ const changeSet = { loading: false, error };
+ if (error.translation_key === 'LOGIN_MAXIMUM_EXCEEDED') {
+ changeSet.requireRecaptcha = !!this.context.store.getState().static
+ .TALK_RECAPTCHA_PUBLIC;
+ }
+ this.setState(changeSet);
});
};
@@ -65,7 +71,7 @@ export default hoistStatics(WrappedComponent => {
signIn={this.signIn}
loading={this.state.loading}
errorMessage={this.getErrorMessage()}
- requireRecaptcha={false}
+ requireRecaptcha={this.state.requireRecaptcha}
/>
);
}
diff --git a/client/coral-framework/services/bootstrap.js b/client/coral-framework/services/bootstrap.js
index 9b5982edc..fa1f907af 100644
--- a/client/coral-framework/services/bootstrap.js
+++ b/client/coral-framework/services/bootstrap.js
@@ -35,7 +35,7 @@ import { setStaticConfiguration } from '../actions/static';
const getAuthToken = (store, storage) => {
let state = store.getState();
- if (state.config.auth_token) {
+ if (state.config && state.config.auth_token) {
// if an auth_token exists in config, use it.
return state.config.auth_token;
} else if (!bowser.safari && !bowser.ios && storage) {
diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js
index 8f9fa8080..6f99a2291 100644
--- a/client/coral-framework/services/plugins.js
+++ b/client/coral-framework/services/plugins.js
@@ -6,6 +6,7 @@ import flattenDeep from 'lodash/flattenDeep';
import isEmpty from 'lodash/isEmpty';
import flatten from 'lodash/flatten';
import mapValues from 'lodash/mapValues';
+import get from 'lodash/get';
import { getDisplayName } from 'coral-framework/helpers/hoc';
import camelize from '../helpers/camelize';
@@ -83,7 +84,7 @@ class PluginsService {
* query datas are only passed to the component if it is defined in `component.fragments`.
*/
getSlotComponentProps(component, reduxState, props, queryData) {
- const pluginConfig = reduxState.config.plugin_config || emptyConfig;
+ const pluginConfig = get(reduxState, 'config.plugin_config') || emptyConfig;
return {
...props,
config: pluginConfig,
@@ -97,7 +98,7 @@ class PluginsService {
* Returns React Elements for given slot.
*/
getSlotElements(slot, reduxState, props = {}, queryData = {}) {
- const pluginConfig = reduxState.config.plugin_config || emptyConfig;
+ const pluginConfig = get(reduxState, 'config.plugin_config') || emptyConfig;
const isDisabled = component => {
if (