diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js
index d226ac744..71d2ed1a3 100644
--- a/client/coral-admin/src/components/UserDetail.js
+++ b/client/coral-admin/src/components/UserDetail.js
@@ -2,7 +2,6 @@ import React from 'react';
import cn from 'classnames';
import PropTypes from 'prop-types';
import capitalize from 'lodash/capitalize';
-import { getErrorMessages } from 'coral-framework/utils';
import styles from './UserDetail.css';
import AccountHistory from './AccountHistory';
import { Slot } from 'coral-framework/components';
@@ -29,43 +28,23 @@ import UserInfoTooltip from './UserInfoTooltip';
class UserDetail extends React.Component {
rejectThenReload = async info => {
- try {
- await this.props.rejectComment(info);
- this.props.data.refetch();
- } catch (err) {
- console.error(err);
- this.props.notify('error', getErrorMessages(err));
- }
+ await this.props.rejectComment(info);
+ this.props.data.refetch();
};
acceptThenReload = async info => {
- try {
- await this.props.acceptComment(info);
- this.props.data.refetch();
- } catch (err) {
- console.error(err);
- this.props.notify('error', getErrorMessages(err));
- }
+ await this.props.acceptComment(info);
+ this.props.data.refetch();
};
bulkAcceptThenReload = async () => {
- try {
- await this.props.bulkAccept();
- this.props.data.refetch();
- } catch (err) {
- console.error(err);
- this.props.notify('error', getErrorMessages(err));
- }
+ await this.props.bulkAccept();
+ this.props.data.refetch();
};
bulkRejectThenReload = async () => {
- try {
- await this.props.bulkReject();
- this.props.data.refetch();
- } catch (err) {
- console.error(err);
- this.props.notify('error', getErrorMessages(err));
- }
+ await this.props.bulkReject();
+ this.props.data.refetch();
};
changeTab = tab => {
@@ -94,6 +73,16 @@ class UserDetail extends React.Component {
);
}
+ renderError() {
+ return (
+
+
+ {this.props.data.error.message}
+
+
+ );
+ }
+
getActionMenuLabel() {
const { root: { user } } = this.props;
@@ -345,6 +334,10 @@ class UserDetail extends React.Component {
}
render() {
+ if (this.props.data.error) {
+ return this.renderError();
+ }
+
if (this.props.loading) {
return this.renderLoading();
}
@@ -371,7 +364,6 @@ UserDetail.propTypes = {
selectedCommentIds: PropTypes.array.isRequired,
viewUserDetail: PropTypes.any.isRequired,
loadMore: PropTypes.any.isRequired,
- notify: PropTypes.func.isRequired,
showSuspendUserDialog: PropTypes.func,
showBanUserDialog: PropTypes.func,
unbanUser: PropTypes.func.isRequired,
diff --git a/client/coral-admin/src/containers/BanUserDialog.js b/client/coral-admin/src/containers/BanUserDialog.js
index d2dc642a7..3617902ff 100644
--- a/client/coral-admin/src/containers/BanUserDialog.js
+++ b/client/coral-admin/src/containers/BanUserDialog.js
@@ -10,8 +10,6 @@ import {
} from 'coral-framework/graphql/mutations';
import { compose } from 'react-apollo';
import t from 'coral-framework/services/i18n';
-import { getErrorMessages } from 'coral-framework/utils';
-import { notify } from 'coral-framework/actions/notification';
class BanUserDialogContainer extends Component {
banUser = async () => {
@@ -22,16 +20,11 @@ class BanUserDialogContainer extends Component {
banUser,
setCommentStatus,
hideBanUserDialog,
- notify,
} = this.props;
- try {
- await banUser({ id: userId, message: '' });
- hideBanUserDialog();
- if (commentId && commentStatus && commentStatus !== 'REJECTED') {
- await setCommentStatus({ commentId, status: 'REJECTED' });
- }
- } catch (err) {
- notify('error', getErrorMessages(err));
+ await banUser({ id: userId, message: '' });
+ hideBanUserDialog();
+ if (commentId && commentStatus && commentStatus !== 'REJECTED') {
+ await setCommentStatus({ commentId, status: 'REJECTED' });
}
};
@@ -78,14 +71,13 @@ const mapDispatchToProps = dispatch => ({
...bindActionCreators(
{
hideBanUserDialog,
- notify,
},
dispatch
),
});
export default compose(
+ connect(mapStateToProps, mapDispatchToProps),
withBanUser,
- withSetCommentStatus,
- connect(mapStateToProps, mapDispatchToProps)
+ withSetCommentStatus
)(BanUserDialogContainer);
diff --git a/client/coral-admin/src/containers/SuspendUserDialog.js b/client/coral-admin/src/containers/SuspendUserDialog.js
index e058faad6..f03d48f52 100644
--- a/client/coral-admin/src/containers/SuspendUserDialog.js
+++ b/client/coral-admin/src/containers/SuspendUserDialog.js
@@ -11,7 +11,6 @@ import {
import { compose, gql } from 'react-apollo';
import t, { timeago } from 'coral-framework/services/i18n';
import withQuery from 'coral-framework/hocs/withQuery';
-import { getErrorMessages } from 'coral-framework/utils';
import get from 'lodash/get';
import { notify } from 'coral-framework/actions/notification';
@@ -28,17 +27,13 @@ class SuspendUserDialogContainer extends Component {
notify,
} = this.props;
hideSuspendUserDialog();
- try {
- await suspendUser({ id: userId, message, until });
- notify(
- 'success',
- t('suspenduser.notify_suspend_until', username, timeago(until))
- );
- if (commentId && commentStatus && commentStatus !== 'REJECTED') {
- await setCommentStatus({ commentId, status: 'REJECTED' });
- }
- } catch (err) {
- notify('error', getErrorMessages(err));
+ await suspendUser({ id: userId, message, until });
+ notify(
+ 'success',
+ t('suspenduser.notify_suspend_until', username, timeago(until))
+ );
+ if (commentId && commentStatus && commentStatus !== 'REJECTED') {
+ await setCommentStatus({ commentId, status: 'REJECTED' });
}
};
diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js
index bd34ac4d8..fb1508a2f 100644
--- a/client/coral-admin/src/containers/UserDetail.js
+++ b/client/coral-admin/src/containers/UserDetail.js
@@ -24,7 +24,6 @@ import {
} from 'coral-framework/graphql/mutations';
import UserDetailComment from './UserDetailComment';
import update from 'immutability-helper';
-import { notify } from 'coral-framework/actions/notification';
import { showBanUserDialog } from 'actions/banUserDialog';
import { showSuspendUserDialog } from 'actions/suspendUserDialog';
@@ -130,6 +129,7 @@ class UserDetailContainer extends React.Component {
acceptComment={this.acceptComment}
rejectComment={this.rejectComment}
loading={loading}
+ error={this.props.data && this.props.data.error}
loadMore={this.loadMore}
{...this.props}
/>
@@ -271,7 +271,6 @@ const mapDispatchToProps = dispatch => ({
viewUserDetail,
hideUserDetail,
toggleSelectAllCommentInUserDetail,
- notify,
},
dispatch
),
diff --git a/client/coral-admin/src/routes/Community/containers/FlaggedAccounts.js b/client/coral-admin/src/routes/Community/containers/FlaggedAccounts.js
index 09240b8f3..1d7e089e2 100644
--- a/client/coral-admin/src/routes/Community/containers/FlaggedAccounts.js
+++ b/client/coral-admin/src/routes/Community/containers/FlaggedAccounts.js
@@ -15,7 +15,6 @@ import { handleFlaggedUsernameChange } from '../graphql';
import { notify } from 'coral-framework/actions/notification';
import { isFlaggedUserDangling } from '../utils';
import t from 'coral-framework/services/i18n';
-import { notifyOnMutationError } from 'coral-framework/hocs';
import FlaggedAccounts from '../components/FlaggedAccounts';
import FlaggedUser from '../containers/FlaggedUser';
@@ -295,7 +294,6 @@ const mapDispatchToProps = dispatch =>
export default compose(
connect(null, mapDispatchToProps),
withApproveUsername,
- notifyOnMutationError(['approveUsername']),
withQuery(
gql`
query TalkAdmin_Community_FlaggedAccounts {
diff --git a/client/coral-admin/src/routes/Community/containers/RejectUsernameDialog.js b/client/coral-admin/src/routes/Community/containers/RejectUsernameDialog.js
index f9ada0945..325eadfb5 100644
--- a/client/coral-admin/src/routes/Community/containers/RejectUsernameDialog.js
+++ b/client/coral-admin/src/routes/Community/containers/RejectUsernameDialog.js
@@ -4,8 +4,6 @@ import { hideRejectUsernameDialog } from '../../../actions/community';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { compose } from 'react-apollo';
-import { notify } from 'coral-framework/actions/notification';
-import { notifyOnMutationError } from 'coral-framework/hocs';
const mapStateToProps = state => ({
user: state.community.user,
@@ -16,13 +14,11 @@ const mapDispatchToProps = dispatch =>
bindActionCreators(
{
handleClose: hideRejectUsernameDialog,
- notify,
},
dispatch
);
export default compose(
connect(mapStateToProps, mapDispatchToProps),
- withRejectUsername,
- notifyOnMutationError(['rejectUsername'])
+ withRejectUsername
)(RejectUsernameDialog);
diff --git a/client/coral-admin/src/routes/Configure/containers/Configure.js b/client/coral-admin/src/routes/Configure/containers/Configure.js
index 0d9d5faf9..ce33fa1f4 100644
--- a/client/coral-admin/src/routes/Configure/containers/Configure.js
+++ b/client/coral-admin/src/routes/Configure/containers/Configure.js
@@ -4,10 +4,9 @@ import { bindActionCreators } from 'redux';
import { compose, gql } from 'react-apollo';
import { withQuery, withMergedSettings } from 'coral-framework/hocs';
import { Spinner } from 'coral-ui';
-import { notify } from 'coral-framework/actions/notification';
import PropTypes from 'prop-types';
import { withUpdateSettings } from 'coral-framework/graphql/mutations';
-import { getErrorMessages, getDefinitionName } from 'coral-framework/utils';
+import { getDefinitionName } from 'coral-framework/utils';
import StreamSettings from './StreamSettings';
import TechSettings from './TechSettings';
import ModerationSettings from './ModerationSettings';
@@ -16,22 +15,21 @@ import Configure from '../components/Configure';
class ConfigureContainer extends Component {
savePending = async () => {
- try {
- await this.props.updateSettings(this.props.pending);
- this.props.clearPending();
- } catch (err) {
- this.props.notify('error', getErrorMessages(err));
- }
+ await this.props.updateSettings(this.props.pending);
+ this.props.clearPending();
};
render() {
+ if (this.props.data.error) {
+ return
{this.props.data.error.message}
;
+ }
+
if (this.props.data.loading) {
return ;
}
return (
({
variables: {},
+ fetchPolicy: 'network-only',
}),
}
);
@@ -81,7 +80,6 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
- notify,
clearPending,
setActiveSection,
},
@@ -89,9 +87,9 @@ const mapDispatchToProps = dispatch =>
);
export default compose(
+ connect(mapStateToProps, mapDispatchToProps),
withUpdateSettings,
withConfigureQuery,
- connect(mapStateToProps, mapDispatchToProps),
withMergedSettings('root.settings', 'pending', 'mergedSettings')
)(ConfigureContainer);
@@ -99,7 +97,6 @@ ConfigureContainer.propTypes = {
updateSettings: PropTypes.func.isRequired,
clearPending: PropTypes.func.isRequired,
setActiveSection: PropTypes.func.isRequired,
- notify: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired,
data: PropTypes.object.isRequired,
root: PropTypes.object.isRequired,
diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js
index fd96c598b..e0e3f14fe 100644
--- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js
+++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js
@@ -269,10 +269,6 @@ class ModerationContainer extends Component {
const { root, root: { asset, settings }, data } = this.props;
const assetId = getAssetId(this.props);
- if (data.error) {
- return Error
;
- }
-
if (assetId) {
if (asset === null) {
// Not found.
@@ -280,6 +276,10 @@ class ModerationContainer extends Component {
}
}
+ if (data.error) {
+ return {data.error.message}
;
+ }
+
if (data.loading && data.networkStatus !== 3) {
// loading.
return ;
diff --git a/client/coral-embed-stream/src/tabs/configure/containers/Configure.js b/client/coral-embed-stream/src/tabs/configure/containers/Configure.js
index c408d67fc..b6104b067 100644
--- a/client/coral-embed-stream/src/tabs/configure/containers/Configure.js
+++ b/client/coral-embed-stream/src/tabs/configure/containers/Configure.js
@@ -9,6 +9,10 @@ import { getDefinitionName } from 'coral-framework/utils';
class ConfigureContainer extends React.Component {
render() {
+ if (this.props.data.error) {
+ return {this.props.data.error.message}
;
+ }
+
return (
{
- try {
- await this.props.updateAssetSettings(
- this.props.asset.id,
- this.props.pending
- );
- this.props.clearPending();
- } catch (err) {
- this.props.notify('error', getErrorMessages(err));
- }
+ await this.props.updateAssetSettings(
+ this.props.asset.id,
+ this.props.pending
+ );
+ this.props.clearPending();
};
render() {
@@ -98,7 +90,6 @@ SettingsContainer.propTypes = {
mergedSettings: PropTypes.object.isRequired,
updateAssetSettings: PropTypes.func.isRequired,
clearPending: PropTypes.func.isRequired,
- notify: PropTypes.func.isRequired,
updatePending: PropTypes.func.isRequired,
canSave: PropTypes.bool.isRequired,
};
@@ -135,7 +126,6 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
- notify,
clearPending,
updatePending,
},
@@ -143,9 +133,9 @@ const mapDispatchToProps = dispatch =>
);
const enhance = compose(
+ connect(mapStateToProps, mapDispatchToProps),
withSettingsFragments,
withUpdateAssetSettings,
- connect(mapStateToProps, mapDispatchToProps),
withMergedSettings('asset.settings', 'pending', 'mergedSettings')
);
diff --git a/client/coral-embed-stream/src/tabs/stream/components/AllCommentsPane.js b/client/coral-embed-stream/src/tabs/stream/components/AllCommentsPane.js
index ed1fe73f9..3190ab155 100644
--- a/client/coral-embed-stream/src/tabs/stream/components/AllCommentsPane.js
+++ b/client/coral-embed-stream/src/tabs/stream/components/AllCommentsPane.js
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import LoadMore from './LoadMore';
import NewCount from './NewCount';
import { TransitionGroup } from 'react-transition-group';
-import { forEachError } from 'coral-framework/utils';
import Comment from '../containers/Comment';
import NoComments from './NoComments';
@@ -91,11 +90,8 @@ class AllCommentsPane extends React.Component {
.then(() => {
this.setState({ loadingState: 'success' });
})
- .catch(error => {
+ .catch(() => {
this.setState({ loadingState: 'error' });
- forEachError(error, ({ msg }) => {
- this.props.notify('error', msg);
- });
});
};
diff --git a/client/coral-embed-stream/src/tabs/stream/components/Comment.js b/client/coral-embed-stream/src/tabs/stream/components/Comment.js
index f4e8404e4..cd0fab9e9 100644
--- a/client/coral-embed-stream/src/tabs/stream/components/Comment.js
+++ b/client/coral-embed-stream/src/tabs/stream/components/Comment.js
@@ -24,7 +24,6 @@ import { EditableCommentContent } from './EditableCommentContent';
import {
getActionSummary,
iPerformedThisAction,
- forEachError,
isCommentActive,
getShallowChanges,
} from 'coral-framework/utils';
@@ -261,11 +260,8 @@ export default class Comment extends React.Component {
loadingState: 'success',
});
})
- .catch(error => {
+ .catch(() => {
this.setState({ loadingState: 'error' });
- forEachError(error, ({ msg }) => {
- this.props.notify('error', msg);
- });
});
emit('ui.Comment.showMoreReplies', { id });
return;
diff --git a/client/coral-embed-stream/src/tabs/stream/components/EditableCommentContent.js b/client/coral-embed-stream/src/tabs/stream/components/EditableCommentContent.js
index bfd2c8bad..f4cfc42e8 100644
--- a/client/coral-embed-stream/src/tabs/stream/components/EditableCommentContent.js
+++ b/client/coral-embed-stream/src/tabs/stream/components/EditableCommentContent.js
@@ -6,7 +6,6 @@ import styles from './Comment.css';
import { CountdownSeconds } from './CountdownSeconds';
import { getEditableUntilDate } from './util';
import { can } from 'coral-framework/services/perms';
-import { forEachError } from 'coral-framework/utils';
import { Icon } from 'coral-ui';
import t from 'coral-framework/services/i18n';
@@ -80,7 +79,7 @@ export class EditableCommentContent extends React.Component {
this.setState({ loadingState: 'loading' });
- const { editComment, notify, stopEditing } = this.props;
+ const { editComment, stopEditing } = this.props;
if (typeof editComment !== 'function') {
return;
}
@@ -95,7 +94,6 @@ export class EditableCommentContent extends React.Component {
}
} catch (error) {
this.setState({ loadingState: 'error' });
- forEachError(error, ({ msg }) => notify('error', msg));
}
};
diff --git a/client/coral-embed-stream/src/tabs/stream/components/Stream.js b/client/coral-embed-stream/src/tabs/stream/components/Stream.js
index d82f49498..6288a2b6b 100644
--- a/client/coral-embed-stream/src/tabs/stream/components/Stream.js
+++ b/client/coral-embed-stream/src/tabs/stream/components/Stream.js
@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { StreamError } from './StreamError';
+import StreamError from './StreamError';
import Comment from '../containers/Comment';
import BannedAccount from '../../../components/BannedAccount';
import ChangeUsername from '../containers/ChangeUsername';
diff --git a/client/coral-embed-stream/src/tabs/stream/components/StreamError.js b/client/coral-embed-stream/src/tabs/stream/components/StreamError.js
index 0955de2ef..79d6b03fc 100644
--- a/client/coral-embed-stream/src/tabs/stream/components/StreamError.js
+++ b/client/coral-embed-stream/src/tabs/stream/components/StreamError.js
@@ -1,6 +1,6 @@
import React from 'react';
import styles from './StreamError.css';
-export const StreamError = ({ children }) => (
+export default ({ children }) => (
{children}
);
diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js
index 70cbadae5..51b4d9d9e 100644
--- a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js
+++ b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js
@@ -38,6 +38,7 @@ import {
insertFetchedCommentsIntoEmbedQuery,
nest,
} from '../../../graphql/utils';
+import StreamError from '../components/StreamError';
const { showSignInDialog, editName } = authActions;
const { notify } = notificationActions;
@@ -208,6 +209,10 @@ class StreamContainer extends React.Component {
}
render() {
+ if (this.props.data.error) {
+ return {this.props.data.error.message};
+ }
+
if (
!this.props.asset ||
(this.props.asset.comment === undefined && !this.props.asset.comments)
@@ -424,7 +429,8 @@ export default compose(
withEmit,
connect(mapStateToProps, mapDispatchToProps),
withPostComment,
- withPostFlag,
+ // `talk-plugin-flags` has a custom error handling logic.
+ withPostFlag({ notifyOnError: false }),
withPostDontAgree,
withDeleteAction,
withEditComment
diff --git a/client/coral-framework/hocs/index.js b/client/coral-framework/hocs/index.js
index 275f6df42..174f25a63 100644
--- a/client/coral-framework/hocs/index.js
+++ b/client/coral-framework/hocs/index.js
@@ -6,4 +6,3 @@ export { default as withEmit } from './withEmit';
export { default as excludeIf } from './excludeIf';
export { default as connect } from './connect';
export { default as withMergedSettings } from './withMergedSettings';
-export { default as notifyOnMutationError } from './notifyOnMutationError';
diff --git a/client/coral-framework/hocs/notifyOnMutationError.js b/client/coral-framework/hocs/notifyOnMutationError.js
deleted file mode 100644
index c9429ad15..000000000
--- a/client/coral-framework/hocs/notifyOnMutationError.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
-import { compose } from 'react-apollo';
-import { notify } from 'coral-framework/actions/notification';
-import { forEachError } from 'coral-framework/utils';
-import { withProps } from 'recompose';
-
-const notifyOnMutationError = keys =>
- compose(
- connect(null, dispatch =>
- bindActionCreators(
- {
- notify,
- },
- dispatch
- )
- ),
- withProps(ownProps =>
- keys.reduce((props, key) => {
- props[key] = async (...args) => {
- try {
- return await ownProps[key](...args);
- } catch (e) {
- forEachError(e, ({ msg }) => {
- ownProps.notify('error', msg);
- });
- throw e;
- }
- };
- return props;
- }, {})
- )
- );
-
-export default notifyOnMutationError;
diff --git a/client/coral-framework/hocs/withMutation.js b/client/coral-framework/hocs/withMutation.js
index b72e56f40..dae38071f 100644
--- a/client/coral-framework/hocs/withMutation.js
+++ b/client/coral-framework/hocs/withMutation.js
@@ -4,11 +4,16 @@ import merge from 'lodash/merge';
import uniq from 'lodash/uniq';
import flatten from 'lodash/flatten';
import isEmpty from 'lodash/isEmpty';
-import { getDefinitionName, getResponseErrors } from '../utils';
+import {
+ getDefinitionName,
+ getResponseErrors,
+ getErrorMessages,
+} from '../utils';
import PropTypes from 'prop-types';
import t from 'coral-framework/services/i18n';
import hoistStatics from 'recompose/hoistStatics';
import union from 'lodash/union';
+import { notify } from 'coral-framework/actions/notification';
class ResponseErrors extends Error {
constructor(errors) {
@@ -27,11 +32,7 @@ class ResponseError {
}
}
-/**
- * Exports a HOC with the same signature as `graphql`, that will
- * apply mutation options registered in the graphRegistry.
- */
-export default (document, config = {}) =>
+const createHOC = (document, config, { notifyOnError = true }) =>
hoistStatics(WrappedComponent => {
config = {
...config,
@@ -46,10 +47,18 @@ export default (document, config = {}) =>
graphql: PropTypes.object,
};
+ static propTypes = {
+ notify: PropTypes.func,
+ };
+
get graphqlRegistry() {
return this.context.graphql.registry;
}
+ notifyErrors(messages) {
+ this.context.store.dispatch(notify('error', messages));
+ }
+
resolveDocument(documentOrCallback) {
return this.context.graphql.resolveDocument(
documentOrCallback,
@@ -165,6 +174,11 @@ export default (document, config = {}) =>
variables,
error,
});
+
+ // Show errors as notifications.
+ if (notifyOnError) {
+ this.notifyErrors(getErrorMessages(error));
+ }
throw error;
});
};
@@ -213,3 +227,18 @@ export default (document, config = {}) =>
}
};
});
+
+/**
+ * Exports a HOC with the same signature as `graphql`, that will
+ * apply mutation options registered in the graphRegistry.
+ *
+ * The returned HOC accepts a settings object with the following properties:
+ * notifyOnError: show a notification to the user when an error occured.
+ * Defaults to true.
+ */
+export default (document, config = {}) => settingsOrComponent => {
+ if (typeof settingsOrComponent === 'function') {
+ return createHOC(document, config, {})(settingsOrComponent);
+ }
+ return createHOC(document, config, settingsOrComponent);
+};
diff --git a/client/coral-framework/hocs/withQuery.js b/client/coral-framework/hocs/withQuery.js
index d5609c340..c4b9074b4 100644
--- a/client/coral-framework/hocs/withQuery.js
+++ b/client/coral-framework/hocs/withQuery.js
@@ -9,6 +9,8 @@ import PropTypes from 'prop-types';
import hoistStatics from 'recompose/hoistStatics';
import { getOperationName } from 'apollo-client/queries/getFromAST';
import throttle from 'lodash/throttle';
+import get from 'lodash/get';
+import { notify } from 'coral-framework/actions/notification';
const withSkipOnErrors = reducer => (prev, action, ...rest) => {
if (
@@ -36,21 +38,23 @@ function networkStatusToString(networkStatus) {
return 'ready';
case 8:
return 'error';
+ default:
+ throw new Error(`Unknown network status ${networkStatus}`);
}
- throw new Error(`Unknown network status ${networkStatus}`);
}
-/**
- * Exports a HOC with the same signature as `graphql`, that will
- * apply query options registered in the graphRegistry.
- */
-export default (document, config = {}) =>
+const createHOC = (document, config, { notifyOnError = true }) =>
hoistStatics(WrappedComponent => {
return class WithQuery extends React.Component {
static contextTypes = {
eventEmitter: PropTypes.object,
graphql: PropTypes.object,
client: PropTypes.object,
+ store: PropTypes.object,
+ };
+
+ static propTypes = {
+ notify: PropTypes.func,
};
// Lazily resolve fragments from graphRegistry to support circular dependencies.
@@ -166,10 +170,24 @@ export default (document, config = {}) =>
return () => this.client.networkInterface.unsubscribe(id);
};
+ notifyErrors(messages) {
+ this.context.store.dispatch(notify('error', messages));
+ }
+
nextData(data) {
this.apolloData = data;
this.emitWhenNeeded(data);
+ if (
+ get(data, 'error.message') &&
+ get(this, 'data.error.message') !== get(data, 'error.message')
+ ) {
+ // Show errors as notifications.
+ if (notifyOnError) {
+ this.notifyErrors(data.error.message);
+ }
+ }
+
// If data was previously set, we update it in a immutable way.
if (this.data) {
if (this.data.loading && !data.loading) {
@@ -319,3 +337,18 @@ export default (document, config = {}) =>
}
};
});
+
+/**
+ * Exports a HOC with the same signature as `graphql`, that will
+ * apply query options registered in the graphRegistry.
+ *
+ * The returned HOC accepts a settings object with the following properties:
+ * notifyOnError: show a notification to the user when an error occured.
+ * Defaults to true.
+ */
+export default (document, config = {}) => settingsOrComponent => {
+ if (typeof settingsOrComponent === 'function') {
+ return createHOC(document, config, {})(settingsOrComponent);
+ }
+ return createHOC(document, config, settingsOrComponent);
+};
diff --git a/client/coral-settings/containers/ProfileContainer.js b/client/coral-settings/containers/ProfileContainer.js
index fb9d8de17..e1bcf7f13 100644
--- a/client/coral-settings/containers/ProfileContainer.js
+++ b/client/coral-settings/containers/ProfileContainer.js
@@ -65,6 +65,10 @@ class ProfileContainer extends Component {
const { me } = this.props.root;
const loading = this.props.data.loading;
+ if (this.props.data.error) {
+ return {this.props.data.error.message}
;
+ }
+
if (!auth.loggedIn) {
return ;
}
diff --git a/client/talk-plugin-commentbox/CommentBox.js b/client/talk-plugin-commentbox/CommentBox.js
index a8da78f3f..8eac1ff2f 100644
--- a/client/talk-plugin-commentbox/CommentBox.js
+++ b/client/talk-plugin-commentbox/CommentBox.js
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import t from 'coral-framework/services/i18n';
import { can } from 'coral-framework/services/perms';
-import { forEachError } from 'coral-framework/utils';
import Slot from 'coral-framework/components/Slot';
import { connect } from 'react-redux';
@@ -93,9 +92,8 @@ class CommentBox extends React.Component {
commentPostedHandler();
}
})
- .catch(err => {
+ .catch(() => {
this.setState({ loadingState: 'error' });
- forEachError(err, ({ msg }) => notify('error', msg));
});
};
diff --git a/client/talk-plugin-flags/components/FlagButton.js b/client/talk-plugin-flags/components/FlagButton.js
index 64841a893..b2048e105 100644
--- a/client/talk-plugin-flags/components/FlagButton.js
+++ b/client/talk-plugin-flags/components/FlagButton.js
@@ -60,9 +60,10 @@ export default class FlagButton extends Component {
});
};
- onPopupContinue = () => {
+ onPopupContinue = async () => {
const { postFlag, postDontAgree, id, author_id } = this.props;
const { itemType, reason, step, message } = this.state;
+ let failed = false;
switch (step) {
case 0:
@@ -75,13 +76,9 @@ export default class FlagButton extends Component {
return;
}
break;
- }
-
- // Proceed to the next step or close the menu if we've reached the end
- if (step + 1 >= this.props.getPopupMenu.length) {
- this.closeMenu();
- } else {
- this.setState({ step: step + 1 });
+ case this.props.getPopupMenu.length:
+ this.closeMenu();
+ return;
}
// If itemType and reason are both set, post the action
@@ -96,43 +93,46 @@ export default class FlagButton extends Component {
break;
}
- if (itemType === 'COMMENTS') {
- this.setState({ localPost: 'temp' });
- }
-
let action = {
item_id,
item_type: itemType,
message,
};
+
if (reason === REASONS.comment.noagree) {
- postDontAgree(action)
- .then(({ data }) => {
- if (itemType === 'COMMENTS') {
- this.setState({ localPost: data.createDontAgree.dontagree.id });
- }
- })
- .catch(err => {
- this.props.notify('error', getErrorMessages(err));
- console.error(err);
- });
- } else {
- postFlag({ ...action, reason })
- .then(({ data }) => {
- if (itemType === 'COMMENTS') {
- this.setState({ localPost: data.createFlag.flag.id });
- }
- })
- .catch(errors => {
- forEachError(errors, ({ error, msg }) => {
- if (error.translation_key === 'ALREADY_EXISTS') {
- msg = t('already_flagged_username');
- }
- this.props.notify('error', msg);
+ const result = await postDontAgree(action);
+ try {
+ if (itemType === 'COMMENTS') {
+ this.setState({
+ localPost: result.data.createDontAgree.dontagree.id,
});
+ }
+ } catch (err) {
+ this.props.notify('error', getErrorMessages(err));
+ console.error(err);
+ failed = true;
+ }
+ } else {
+ try {
+ const result = await postFlag({ ...action, reason });
+ if (itemType === 'COMMENTS') {
+ this.setState({ localPost: result.data.createFlag.flag.id });
+ }
+ } catch (errors) {
+ forEachError(errors, ({ error, msg }) => {
+ if (error.translation_key === 'ALREADY_EXISTS') {
+ msg = t('already_flagged_username');
+ }
+ this.props.notify('error', msg);
});
+ failed = true;
+ }
}
}
+
+ if (!failed) {
+ this.setState({ step: step + 1 });
+ }
};
onPopupOptionClick = sets => e => {
diff --git a/client/talk-plugin-history/CommentHistory.js b/client/talk-plugin-history/CommentHistory.js
index a56350c4a..a17a347c5 100644
--- a/client/talk-plugin-history/CommentHistory.js
+++ b/client/talk-plugin-history/CommentHistory.js
@@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import Comment from './Comment';
import LoadMore from './LoadMore';
-import { forEachError } from 'plugin-api/beta/client/utils';
class CommentHistory extends React.Component {
state = {
@@ -16,11 +15,8 @@ class CommentHistory extends React.Component {
.then(() => {
this.setState({ loadingState: 'success' });
})
- .catch(error => {
+ .catch(() => {
this.setState({ loadingState: 'error' });
- forEachError(error, ({ msg }) => {
- this.props.notify('error', msg);
- });
});
};
@@ -55,7 +51,6 @@ class CommentHistory extends React.Component {
CommentHistory.propTypes = {
comments: PropTypes.object.isRequired,
loadMore: PropTypes.func,
- notify: PropTypes.func,
link: PropTypes.func,
data: PropTypes.object,
root: PropTypes.object,
diff --git a/plugin-api/beta/client/hocs/withReaction.js b/plugin-api/beta/client/hocs/withReaction.js
index f5a0167b7..6020d4002 100644
--- a/plugin-api/beta/client/hocs/withReaction.js
+++ b/plugin-api/beta/client/hocs/withReaction.js
@@ -9,11 +9,7 @@ import withFragments from 'coral-framework/hocs/withFragments';
import withMutation from 'coral-framework/hocs/withMutation';
import { notify } from 'coral-framework/actions/notification';
import { capitalize } from 'coral-framework/helpers/strings';
-import {
- getMyActionSummary,
- getTotalActionCount,
- getErrorMessages,
-} from 'coral-framework/utils';
+import { getMyActionSummary, getTotalActionCount } from 'coral-framework/utils';
import hoistStatics from 'recompose/hoistStatics';
import * as PropTypes from 'prop-types';
import { getDefinitionName } from '../utils';
@@ -282,7 +278,6 @@ export default (reaction, options = {}) =>
})
.catch(err => {
this.duringMutation = false;
- this.props.notify('error', getErrorMessages(err));
throw err;
});
};
@@ -307,7 +302,6 @@ export default (reaction, options = {}) =>
})
.catch(err => {
this.duringMutation = false;
- this.props.notify('error', getErrorMessages(err));
throw err;
});
};
diff --git a/plugin-api/beta/client/hocs/withTags.js b/plugin-api/beta/client/hocs/withTags.js
index 7b95a5e6c..958028a4b 100644
--- a/plugin-api/beta/client/hocs/withTags.js
+++ b/plugin-api/beta/client/hocs/withTags.js
@@ -1,13 +1,11 @@
import React from 'react';
import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
import { compose, gql } from 'react-apollo';
import { getDisplayName } from 'coral-framework/helpers/hoc';
import { capitalize } from 'coral-framework/helpers/strings';
import { withAddTag, withRemoveTag } from 'coral-framework/graphql/mutations';
import withFragments from 'coral-framework/hocs/withFragments';
-import { notify } from 'coral-framework/actions/notification';
-import { getErrorMessages, isTagged } from 'coral-framework/utils';
+import { isTagged } from 'coral-framework/utils';
import hoistStatics from 'recompose/hoistStatics';
import { getDefinitionName } from '../utils';
@@ -38,7 +36,7 @@ export default (tag, options = {}) =>
loading = false;
postTag = () => {
- const { comment, asset, notify } = this.props;
+ const { comment, asset } = this.props;
if (this.loading) {
return;
@@ -59,13 +57,12 @@ export default (tag, options = {}) =>
})
.catch(err => {
this.loading = false;
- notify('error', getErrorMessages(err));
throw err;
});
};
deleteTag = () => {
- const { comment, asset, notify } = this.props;
+ const { comment, asset } = this.props;
if (this.loading) {
return;
@@ -84,7 +81,6 @@ export default (tag, options = {}) =>
})
.catch(err => {
this.loading = false;
- notify('error', getErrorMessages(err));
throw err;
});
};
@@ -114,9 +110,6 @@ export default (tag, options = {}) =>
user: state.auth.user,
});
- const mapDispatchToProps = dispatch =>
- bindActionCreators({ notify }, dispatch);
-
const enhance = compose(
withFragments({
...fragments,
@@ -146,7 +139,7 @@ export default (tag, options = {}) =>
}),
withAddTag,
withRemoveTag,
- connect(mapStateToProps, mapDispatchToProps)
+ connect(mapStateToProps, null)
);
WithTags.displayName = `WithTags(${getDisplayName(WrappedComponent)})`;
diff --git a/plugins/talk-plugin-featured-comments/client/components/ModTag.js b/plugins/talk-plugin-featured-comments/client/components/ModTag.js
index 420ee9417..659b2cea4 100644
--- a/plugins/talk-plugin-featured-comments/client/components/ModTag.js
+++ b/plugins/talk-plugin-featured-comments/client/components/ModTag.js
@@ -64,7 +64,6 @@ export default class ModTag extends React.Component {
ModTag.propTypes = {
alreadyTagged: PropTypes.bool,
deleteTag: PropTypes.func,
- notify: PropTypes.func,
openFeaturedDialog: PropTypes.func,
comment: PropTypes.object,
asset: PropTypes.object,
diff --git a/plugins/talk-plugin-featured-comments/client/components/TabPane.js b/plugins/talk-plugin-featured-comments/client/components/TabPane.js
index 084de7d0a..9bdba6fe8 100644
--- a/plugins/talk-plugin-featured-comments/client/components/TabPane.js
+++ b/plugins/talk-plugin-featured-comments/client/components/TabPane.js
@@ -1,7 +1,6 @@
import React from 'react';
import Comment from '../containers/Comment';
import LoadMore from './LoadMore';
-import { getErrorMessages } from 'plugin-api/beta/client/utils';
class TabPane extends React.Component {
state = {
@@ -15,9 +14,8 @@ class TabPane extends React.Component {
.then(() => {
this.setState({ loadingState: 'success' });
})
- .catch(error => {
+ .catch(() => {
this.setState({ loadingState: 'error' });
- this.props.notify('error', getErrorMessages(error));
});
};
diff --git a/plugins/talk-plugin-featured-comments/client/containers/ModActionButton.js b/plugins/talk-plugin-featured-comments/client/containers/ModActionButton.js
index 1f210cb95..7c8f94fb3 100644
--- a/plugins/talk-plugin-featured-comments/client/containers/ModActionButton.js
+++ b/plugins/talk-plugin-featured-comments/client/containers/ModActionButton.js
@@ -13,8 +13,8 @@ const mapDispatchToProps = dispatch =>
);
const enhance = compose(
- withTags('featured'),
- connect(null, mapDispatchToProps)
+ connect(null, mapDispatchToProps),
+ withTags('featured')
);
export default enhance(ModActionButton);
diff --git a/plugins/talk-plugin-featured-comments/client/containers/ModTag.js b/plugins/talk-plugin-featured-comments/client/containers/ModTag.js
index 990a8a2e9..42c653418 100644
--- a/plugins/talk-plugin-featured-comments/client/containers/ModTag.js
+++ b/plugins/talk-plugin-featured-comments/client/containers/ModTag.js
@@ -3,12 +3,10 @@ import { withTags, connect } from 'plugin-api/beta/client/hocs';
import { gql, compose } from 'react-apollo';
import { bindActionCreators } from 'redux';
import { openFeaturedDialog } from '../actions';
-import { notify } from 'plugin-api/beta/client/actions/notification';
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
- notify,
openFeaturedDialog,
},
dispatch
@@ -24,8 +22,8 @@ const fragments = {
`,
};
const enhance = compose(
- withTags('featured', { fragments }),
- connect(null, mapDispatchToProps)
+ connect(null, mapDispatchToProps),
+ withTags('featured', { fragments })
);
export default enhance(ModTag);
diff --git a/plugins/talk-plugin-featured-comments/client/containers/TabPane.js b/plugins/talk-plugin-featured-comments/client/containers/TabPane.js
index 5bf92fb0a..297f5aedf 100644
--- a/plugins/talk-plugin-featured-comments/client/containers/TabPane.js
+++ b/plugins/talk-plugin-featured-comments/client/containers/TabPane.js
@@ -4,7 +4,6 @@ import { compose, gql } from 'react-apollo';
import TabPane from '../components/TabPane';
import { withFragments, connect } from 'plugin-api/beta/client/hocs';
import Comment from '../containers/Comment';
-import { notify } from 'plugin-api/beta/client/actions/notification';
import { viewComment } from 'coral-embed-stream/src/actions/stream';
import {
appendNewNodes,
@@ -81,7 +80,6 @@ const mapDispatchToProps = dispatch =>
bindActionCreators(
{
viewComment,
- notify,
},
dispatch
);
diff --git a/plugins/talk-plugin-ignore-user/client/containers/IgnoreUserConfirmation.js b/plugins/talk-plugin-ignore-user/client/containers/IgnoreUserConfirmation.js
index 733dab756..5aa834034 100644
--- a/plugins/talk-plugin-ignore-user/client/containers/IgnoreUserConfirmation.js
+++ b/plugins/talk-plugin-ignore-user/client/containers/IgnoreUserConfirmation.js
@@ -10,21 +10,16 @@ import { bindActionCreators } from 'redux';
import { closeMenu } from 'plugins/talk-plugin-author-menu/client/actions';
import { notify } from 'plugin-api/beta/client/actions/notification';
import { t } from 'plugin-api/beta/client/services';
-import { getErrorMessages } from 'plugin-api/beta/client/utils';
class IgnoreUserConfirmationContainer extends React.Component {
ignoreUser = () => {
const { ignoreUser, notify, comment, closeMenu } = this.props;
- ignoreUser(comment.user.id)
- .then(() => {
- notify(
- 'success',
- t('talk-plugin-ignore-user.notify_success', comment.user.username)
- );
- })
- .catch(err => {
- notify('error', getErrorMessages(err));
- });
+ ignoreUser(comment.user.id).then(() => {
+ notify(
+ 'success',
+ t('talk-plugin-ignore-user.notify_success', comment.user.username)
+ );
+ });
closeMenu();
};
diff --git a/plugins/talk-plugin-moderation-actions/client/containers/ApproveCommentAction.js b/plugins/talk-plugin-moderation-actions/client/containers/ApproveCommentAction.js
index 70cb2ce4b..1f7bed3bb 100644
--- a/plugins/talk-plugin-moderation-actions/client/containers/ApproveCommentAction.js
+++ b/plugins/talk-plugin-moderation-actions/client/containers/ApproveCommentAction.js
@@ -1,23 +1,16 @@
import React from 'react';
import { compose } from 'react-apollo';
-import { bindActionCreators } from 'redux';
-import { getErrorMessages } from 'plugin-api/beta/client/utils';
-import { notify } from 'plugin-api/beta/client/actions/notification';
import ApproveCommentAction from '../components/ApproveCommentAction';
-import { connect, withSetCommentStatus } from 'plugin-api/beta/client/hocs';
+import { withSetCommentStatus } from 'plugin-api/beta/client/hocs';
class ApproveCommentActionContainer extends React.Component {
approveComment = async () => {
- const { setCommentStatus, comment, hideMenu, notify } = this.props;
+ const { setCommentStatus, comment, hideMenu } = this.props;
- try {
- await setCommentStatus({
- commentId: comment.id,
- status: 'ACCEPTED',
- });
- } catch (err) {
- notify('error', getErrorMessages(err));
- }
+ await setCommentStatus({
+ commentId: comment.id,
+ status: 'ACCEPTED',
+ });
hideMenu();
};
@@ -32,17 +25,6 @@ class ApproveCommentActionContainer extends React.Component {
}
}
-const mapDispatchToProps = dispatch =>
- bindActionCreators(
- {
- notify,
- },
- dispatch
- );
-
-const enhance = compose(
- connect(null, mapDispatchToProps),
- withSetCommentStatus
-);
+const enhance = compose(withSetCommentStatus);
export default enhance(ApproveCommentActionContainer);
diff --git a/plugins/talk-plugin-moderation-actions/client/containers/BanUserDialog.js b/plugins/talk-plugin-moderation-actions/client/containers/BanUserDialog.js
index 6d64addca..8dd3cce7d 100644
--- a/plugins/talk-plugin-moderation-actions/client/containers/BanUserDialog.js
+++ b/plugins/talk-plugin-moderation-actions/client/containers/BanUserDialog.js
@@ -3,19 +3,16 @@ import PropTypes from 'prop-types';
import { compose } from 'react-apollo';
import { bindActionCreators } from 'redux';
import { closeBanDialog, closeMenu } from '../actions';
-import { notify } from 'plugin-api/beta/client/actions/notification';
import {
connect,
withSetCommentStatus,
withBanUser,
} from 'plugin-api/beta/client/hocs';
-import { getErrorMessages } from 'plugin-api/beta/client/utils';
import BanUserDialog from '../components/BanUserDialog';
class BanUserDialogContainer extends React.Component {
banUser = async () => {
const {
- notify,
authorId,
commentId,
commentStatus,
@@ -25,23 +22,19 @@ class BanUserDialogContainer extends React.Component {
banUser,
} = this.props;
- try {
- await banUser({
- id: authorId,
- message: '',
+ await banUser({
+ id: authorId,
+ message: '',
+ });
+
+ closeMenu();
+ closeBanDialog();
+
+ if (commentStatus !== 'REJECTED') {
+ await setCommentStatus({
+ commentId: commentId,
+ status: 'REJECTED',
});
-
- closeMenu();
- closeBanDialog();
-
- if (commentStatus !== 'REJECTED') {
- await setCommentStatus({
- commentId: commentId,
- status: 'REJECTED',
- });
- }
- } catch (err) {
- notify('error', getErrorMessages(err));
}
};
@@ -70,7 +63,6 @@ const mapStateToProps = ({ talkPluginModerationActions: state }) => ({
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
- notify,
closeBanDialog,
closeMenu,
},
diff --git a/plugins/talk-plugin-moderation-actions/client/containers/RejectCommentAction.js b/plugins/talk-plugin-moderation-actions/client/containers/RejectCommentAction.js
index e49fdddba..54ca0420d 100644
--- a/plugins/talk-plugin-moderation-actions/client/containers/RejectCommentAction.js
+++ b/plugins/talk-plugin-moderation-actions/client/containers/RejectCommentAction.js
@@ -1,23 +1,16 @@
import React from 'react';
import { compose } from 'react-apollo';
-import { bindActionCreators } from 'redux';
-import { getErrorMessages } from 'plugin-api/beta/client/utils';
-import { notify } from 'plugin-api/beta/client/actions/notification';
import RejectCommentAction from '../components/RejectCommentAction';
-import { connect, withSetCommentStatus } from 'plugin-api/beta/client/hocs';
+import { withSetCommentStatus } from 'plugin-api/beta/client/hocs';
class RejectCommentActionContainer extends React.Component {
rejectComment = async () => {
- const { setCommentStatus, comment, hideMenu, notify } = this.props;
+ const { setCommentStatus, comment, hideMenu } = this.props;
- try {
- await setCommentStatus({
- commentId: comment.id,
- status: 'REJECTED',
- });
- } catch (err) {
- notify('error', getErrorMessages(err));
- }
+ await setCommentStatus({
+ commentId: comment.id,
+ status: 'REJECTED',
+ });
hideMenu();
};
@@ -27,17 +20,6 @@ class RejectCommentActionContainer extends React.Component {
}
}
-const mapDispatchToProps = dispatch =>
- bindActionCreators(
- {
- notify,
- },
- dispatch
- );
-
-const enhance = compose(
- connect(null, mapDispatchToProps),
- withSetCommentStatus
-);
+const enhance = compose(withSetCommentStatus);
export default enhance(RejectCommentActionContainer);