diff --git a/client/coral-admin/src/components/App.css b/client/coral-admin/src/components/App.css
new file mode 100644
index 000000000..39c7cf942
--- /dev/null
+++ b/client/coral-admin/src/components/App.css
@@ -0,0 +1,11 @@
+:global {
+ html, body, #root, #root > div {
+ min-height: 100%;
+ }
+
+ body {
+ margin: 0;
+ background-color: #FAFAFA;
+ font-family: 'Roboto', sans-serif;
+ }
+}
diff --git a/client/coral-admin/src/components/App.js b/client/coral-admin/src/components/App.js
index 52535f91a..a7ab1e336 100644
--- a/client/coral-admin/src/components/App.js
+++ b/client/coral-admin/src/components/App.js
@@ -1,5 +1,6 @@
import React from 'react';
import ToastContainer from './ToastContainer';
+import './App.css';
import 'material-design-lite';
import AppRouter from '../AppRouter';
diff --git a/client/coral-admin/src/components/Header.js b/client/coral-admin/src/components/Header.js
index fc150b840..f0bb68c0c 100644
--- a/client/coral-admin/src/components/Header.js
+++ b/client/coral-admin/src/components/Header.js
@@ -7,7 +7,6 @@ import styles from './Header.css';
import t from 'coral-framework/services/i18n';
import { Logo } from './Logo';
import { can } from 'coral-framework/services/perms';
-import ModerationIndicator from '../routes/Moderation/containers/Indicator';
import CommunityIndicator from '../routes/Community/containers/Indicator';
const CoralHeader = ({
@@ -32,7 +31,6 @@ const CoralHeader = ({
activeClassName={styles.active}
>
{t('configure.moderate')}
-
)}
{
- return props.root[`${props.activeTab}Count`];
- };
-
moderate = accept => {
const {
acceptComment,
@@ -139,12 +135,14 @@ class Moderation extends Component {
const comments = root[activeTab];
- const activeTabCount = this.getActiveTabCount();
const menuItems = Object.keys(queueConfig).map(queue => ({
key: queue,
name: queueConfig[queue].name,
icon: queueConfig[queue].icon,
- count: root[`${queue}Count`],
+ indicator:
+ ['premod', 'reported'].includes(queue) && root[queue].nodes.length > 0,
+ // TODO: Eventually we'll reintroduce counting
+ // count: root[`${props.queue}Count`]
}));
const slotPassthrough = {
@@ -189,7 +187,6 @@ class Moderation extends Component {
loadMore={this.loadMore}
commentBelongToQueue={this.props.commentBelongToQueue}
isLoadingMore={this.state.isLoadingMore}
- commentCount={activeTabCount}
currentUserId={this.props.currentUser.id}
viewUserDetail={viewUserDetail}
selectCommentId={props.selectCommentId}
diff --git a/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js b/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js
index 75a274eb3..27394c6c9 100644
--- a/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js
+++ b/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js
@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
-import CountBadge from '../../../components/CountBadge';
+import Indicator from '../../../components/Indicator';
import styles from './ModerationMenu.css';
import { Icon } from 'coral-ui';
import { Link } from 'react-router';
@@ -32,7 +32,7 @@ const ModerationMenu = ({ asset = {}, items, getModPath, activeTab }) => {
activeClassName={styles.active}
>
{queue.name}{' '}
-
+ {queue.indicator && }
))}
diff --git a/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js b/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js
index e47a162ef..c22843c8f 100644
--- a/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js
+++ b/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js
@@ -204,7 +204,7 @@ class ModerationQueue extends React.Component {
}
componentDidUpdate(prev) {
- const { commentCount, selectedCommentId } = this.props;
+ const { selectedCommentId, hasNextPage } = this.props;
const switchedToMultiMode = prev.singleView && !this.props.singleView;
const switchedMode = prev.singleView !== this.props.singleView;
@@ -212,7 +212,6 @@ class ModerationQueue extends React.Component {
prev.selectedCommentId !== selectedCommentId && selectedCommentId;
const moderatedLastComment =
prev.comments.length > 0 && this.getCommentCountWithoutDagling() === 0;
- const hasMoreComment = commentCount > 0;
if (switchedToMultiMode) {
// Reflow virtual list.
@@ -223,7 +222,7 @@ class ModerationQueue extends React.Component {
this.scrollToSelectedComment();
}
- if (moderatedLastComment && hasMoreComment) {
+ if (moderatedLastComment && hasNextPage) {
this.props.loadMore();
}
}
@@ -240,10 +239,7 @@ class ModerationQueue extends React.Component {
const index = view.findIndex(
({ id }) => id === this.props.selectedCommentId
);
- if (
- index === view.length - 1 &&
- this.getCommentCountWithoutDagling() !== this.props.commentCount
- ) {
+ if (index === view.length - 1 && this.props.hasNextPage) {
await this.props.loadMore();
this.selectDown();
return;
@@ -467,7 +463,6 @@ ModerationQueue.propTypes = {
acceptComment: PropTypes.func.isRequired,
commentBelongToQueue: PropTypes.func.isRequired,
cleanUpQueue: PropTypes.func.isRequired,
- commentCount: PropTypes.number.isRequired,
loadMore: PropTypes.func.isRequired,
singleView: PropTypes.bool,
isLoadingMore: PropTypes.bool,
diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js
index c908404cb..e3f35081c 100644
--- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js
+++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js
@@ -314,11 +314,11 @@ class ModerationContainer extends Component {
const currentQueueConfig = Object.assign({}, this.props.queueConfig);
- if (premodEnabled && root.newCount === 0) {
+ if (premodEnabled && root.new.nodes.length === 0) {
delete currentQueueConfig.new;
}
- if (!premodEnabled && root.premodCount === 0) {
+ if (!premodEnabled && root.premod.nodes.length === 0) {
delete currentQueueConfig.premod;
}
@@ -402,7 +402,7 @@ const COMMENT_RESET_SUBSCRIPTION = gql`
const LOAD_MORE_QUERY = gql`
query CoralAdmin_Moderation_LoadMore($limit: Int = 10, $cursor: Cursor, $sortOrder: SORT_ORDER, $asset_id: ID, $tags:[String!], $statuses:[COMMENT_STATUS!], $action_type: ACTION_TYPE) {
- comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sortOrder: $sortOrder, action_type: $action_type, tags: $tags}) {
+ comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sortOrder: $sortOrder, action_type: $action_type, tags: $tags, excludeDeleted: true}) {
nodes {
...${getDefinitionName(Comment.fragments.comment)}
}
@@ -456,7 +456,11 @@ const withModQueueQuery = withQuery(
}
`
)}
- ${Object.keys(queueConfig).map(
+ ${
+ ''
+ /*
+ TODO: eventually we'll reintroduce counting..
+ Object.keys(queueConfig).map(
queue => `
${queue}Count: commentCount(query: {
excludeDeleted: true,
@@ -478,7 +482,8 @@ const withModQueueQuery = withQuery(
asset_id: $asset_id,
})
`
- )}
+ )*/
+ }
asset(id: $asset_id) @skip(if: $allAssets) {
id
title
diff --git a/client/coral-embed-stream/src/tabs/profile/components/Comment.css b/client/coral-embed-stream/src/tabs/profile/components/Comment.css
index 9004a1e6c..c2bf841d5 100644
--- a/client/coral-embed-stream/src/tabs/profile/components/Comment.css
+++ b/client/coral-embed-stream/src/tabs/profile/components/Comment.css
@@ -31,6 +31,7 @@
font-weight: bold;
font-size: 12px;
color: #757575;
+ cursor: pointer;
}
.commentSummary {
diff --git a/client/coral-embed-stream/src/tabs/profile/components/Comment.js b/client/coral-embed-stream/src/tabs/profile/components/Comment.js
index 171172dab..badeb4262 100644
--- a/client/coral-embed-stream/src/tabs/profile/components/Comment.js
+++ b/client/coral-embed-stream/src/tabs/profile/components/Comment.js
@@ -11,16 +11,6 @@ import { getTotalReactionsCount } from 'coral-framework/utils';
import t from 'coral-framework/services/i18n';
class Comment extends React.Component {
- goToStory = () => {
- this.props.navigate(this.props.comment.asset.url);
- };
-
- goToConversation = () => {
- this.props.navigate(
- `${this.props.comment.asset.url}?commentId=${this.props.comment.id}`
- );
- };
-
render() {
const { comment, root } = this.props;
const reactionCount = getTotalReactionsCount(comment.action_summaries);
@@ -76,8 +66,8 @@ class Comment extends React.Component {
{t('common.story')}:{' '}
{comment.asset.title ? comment.asset.title : comment.asset.url}
@@ -87,7 +77,13 @@ class Comment extends React.Component {
-
-
+
{t('view_conversation')}
diff --git a/client/coral-framework/components/Popup.js b/client/coral-framework/components/Popup.js
index 53d8f7cd8..339ddb806 100644
--- a/client/coral-framework/components/Popup.js
+++ b/client/coral-framework/components/Popup.js
@@ -37,7 +37,8 @@ export default class Popup extends Component {
this.onBlur();
};
- // Use `onunload` instead of `onbeforeunload` which is not supported in IOS Safari.
+ // Use `onunload` instead of `onbeforeunload` which is not supported in iOS
+ // Safari.
this.ref.onunload = () => {
this.onUnload();
@@ -46,10 +47,15 @@ export default class Popup extends Component {
}
this.resetCallbackInterval = setInterval(() => {
- if (this.ref && this.ref.onload === null) {
- clearInterval(this.resetCallbackInterval);
- this.resetCallbackInterval = null;
- this.setCallbacks();
+ try {
+ if (this.ref && this.ref.onload === null) {
+ clearInterval(this.resetCallbackInterval);
+ this.resetCallbackInterval = null;
+ this.setCallbacks();
+ }
+ } catch (err) {
+ // We could be getting a security exception here if the login page
+ // gets redirected to another domain to authenticate.
}
}, 50);
diff --git a/client/coral-framework/hocs/withSetUsername.js b/client/coral-framework/hocs/withSetUsername.js
index 05323efa1..3a8f65a22 100644
--- a/client/coral-framework/hocs/withSetUsername.js
+++ b/client/coral-framework/hocs/withSetUsername.js
@@ -59,6 +59,7 @@ const withSetUsername = hoistStatics(WrappedComponent => {
}
const changeSet = { success: false, loading: false, error };
this.setState(changeSet);
+ throw error;
}
};
diff --git a/graph/loaders/settings.js b/graph/loaders/settings.js
index 8b07d712b..7010d7533 100644
--- a/graph/loaders/settings.js
+++ b/graph/loaders/settings.js
@@ -55,6 +55,18 @@ class SettingsLoader {
// assembled Settings object.
return zipObject(fields, values);
}
+
+ /**
+ * get, like select, will retrieve the settings, but get will only return a
+ * single setting.
+ *
+ * @param {String} field the field to get
+ */
+ async get(field) {
+ const value = await this._loader.load(field);
+
+ return value;
+ }
}
module.exports = () => ({ Settings: new SettingsLoader() });
diff --git a/graph/resolvers/comment.js b/graph/resolvers/comment.js
index c40aa49e0..acd55cb1a 100644
--- a/graph/resolvers/comment.js
+++ b/graph/resolvers/comment.js
@@ -57,8 +57,8 @@ const Comment = {
asset({ asset_id }, _, { loaders: { Assets } }) {
return Assets.getByID.load(asset_id);
},
- async editing(comment, _, { loaders: { Settings } }) {
- const { editCommentWindowLength } = await Settings.select(
+ editing: async (comment, _, { loaders: { Settings } }) => {
+ const editCommentWindowLength = await Settings.get(
'editCommentWindowLength'
);
diff --git a/middleware/staticTemplate.js b/middleware/staticTemplate.js
index f16caaea9..d8137693e 100644
--- a/middleware/staticTemplate.js
+++ b/middleware/staticTemplate.js
@@ -94,9 +94,13 @@ const createResolveFactory = (() => {
module.exports = async (req, res, next) => {
try {
- // Attach the custom css url.
- const { customCssUrl } = await SettingsService.select('customCssUrl');
+ // Attach the custom css url and organization name.
+ const { customCssUrl, organizationName } = await SettingsService.select(
+ 'customCssUrl',
+ 'organizationName'
+ );
res.locals.customCssUrl = customCssUrl;
+ res.locals.organizationName = organizationName;
} catch (err) {
console.warn(err);
}
diff --git a/models/schema/action.js b/models/schema/action.js
index 1df7bd793..d2047faac 100644
--- a/models/schema/action.js
+++ b/models/schema/action.js
@@ -9,7 +9,8 @@ const Action = new Schema(
id: {
type: String,
default: uuid.v4,
- unique: true,
+ unique: 1,
+ index: 1,
},
action_type: {
type: String,
@@ -19,7 +20,10 @@ const Action = new Schema(
type: String,
enum: ITEM_TYPES,
},
- item_id: String,
+ item_id: {
+ type: String,
+ index: 1,
+ },
user_id: String,
// The element that summaries will additionally group on in addtion to their action_type, item_type, and
@@ -37,15 +41,4 @@ const Action = new Schema(
}
);
-// Create an index on the `item_id` field so that queries looking for
-// actions based on the item id can resolve faster.
-Action.index(
- {
- item_id: 1,
- },
- {
- background: true,
- }
-);
-
module.exports = Action;
diff --git a/models/schema/comment.js b/models/schema/comment.js
index a211884ff..7ae51ff41 100644
--- a/models/schema/comment.js
+++ b/models/schema/comment.js
@@ -55,12 +55,16 @@ const Comment = new Schema(
type: String,
default: uuid.v4,
unique: true,
+ index: true,
},
body: {
type: String,
},
body_history: [BodyHistoryItemSchema],
- asset_id: String,
+ asset_id: {
+ type: String,
+ index: true,
+ },
author_id: String,
status_history: [Status],
status: {
@@ -90,7 +94,6 @@ const Comment = new Schema(
// deleted_at stores the date that the given comment was deleted.
deleted_at: {
type: Date,
- default: null,
},
// Additional metadata stored on the field.
@@ -110,95 +113,67 @@ const Comment = new Schema(
}
);
-// Add the indexes for the id of the comment.
Comment.index(
{
- id: 1,
- },
- {
- unique: true,
- background: false,
- }
-);
-
-Comment.index(
- {
- status: 1,
- created_at: 1,
- },
- {
- background: true,
- }
-);
-
-Comment.index(
- {
- status: 1,
- created_at: 1,
- asset_id: 1,
- },
- {
- background: true,
- }
-);
-
-// Create a sparse index to search across.
-Comment.index(
- {
- created_at: 1,
- 'action_counts.flag': 1,
- status: 1,
- },
- {
- background: true,
- sparse: true,
- }
-);
-
-// Create a sparse index to search across.
-Comment.index(
- {
- 'action_counts.flag': 1,
- status: 1,
- },
- {
- background: true,
- sparse: true,
- }
-);
-
-// Add an index that is optimized for finding flagged comments.
-Comment.index(
- {
- asset_id: 1,
- created_at: 1,
- 'action_counts.flag': 1,
- },
- {
- background: true,
- }
-);
-
-// Add an index for the reply sort.
-Comment.index(
- {
- asset_id: 1,
+ deleted_at: 1,
created_at: -1,
- reply_count: -1,
},
- {
- background: true,
- }
+ { partialFilterExpression: { deleted_at: null } }
);
-// Add an index that is optimized for finding a user's comments.
Comment.index(
{
- author_id: 1,
+ deleted_at: 1,
+ status: 1,
+ created_at: -1,
+ },
+ { partialFilterExpression: { deleted_at: null } }
+);
+
+Comment.index({
+ asset_id: 1,
+ created_at: -1,
+});
+
+Comment.index({
+ asset_id: 1,
+ created_at: 1,
+});
+
+Comment.index({
+ author_id: 1,
+ created_at: -1,
+});
+
+Comment.index({
+ asset_id: 1,
+ status: 1,
+});
+
+Comment.index({
+ asset_id: 1,
+ parent_id: 1,
+ reply_count: -1,
+ created_at: -1,
+});
+
+Comment.index({
+ asset_id: 1,
+ reply_count: -1,
+ created_at: -1,
+});
+
+Comment.index(
+ {
+ 'action_counts.flag': 1,
+ status: 1,
created_at: -1,
},
{
- background: true,
+ partialFilterExpression: {
+ 'action_counts.flag': { $exists: true, $gt: 0 },
+ deleted_at: null,
+ },
}
);
@@ -210,34 +185,10 @@ Comment.index(
status: 1,
},
{
- background: true,
- }
-);
-
-// Optimize for tag searches/counts.
-Comment.index(
- {
- 'tags.tag.name': 1,
- status: 1,
- },
- {
- background: true,
sparse: true,
}
);
-// Add an index that is optimized for sorting based on the created_at timestamp
-// but also good at locating comments that have a specific asset id.
-Comment.index(
- {
- asset_id: 1,
- created_at: 1,
- },
- {
- background: true,
- }
-);
-
Comment.virtual('edited').get(function() {
return this.body_history.length > 1;
});
diff --git a/models/schema/setting.js b/models/schema/setting.js
index d75bed29e..d68ee7acd 100644
--- a/models/schema/setting.js
+++ b/models/schema/setting.js
@@ -12,6 +12,8 @@ const Setting = new Schema(
id: {
type: String,
default: '1',
+ unique: 1,
+ index: true,
},
moderation: {
type: String,
diff --git a/models/schema/user.js b/models/schema/user.js
index ec9c018cc..a4ccfd185 100644
--- a/models/schema/user.js
+++ b/models/schema/user.js
@@ -58,6 +58,7 @@ const User = new Schema(
default: uuid.v4,
unique: true,
required: true,
+ index: true,
},
// This is sourced from the social provider or set manually during user setup
@@ -107,6 +108,7 @@ const User = new Schema(
status: {
type: String,
enum: USER_STATUS_USERNAME,
+ index: true,
},
// History stores the history of username status changes.
@@ -135,6 +137,7 @@ const User = new Schema(
type: Boolean,
required: true,
default: false,
+ index: true,
},
history: [
{
@@ -226,41 +229,26 @@ User.index(
}
);
-User.index(
- {
- lowercaseUsername: 1,
- 'profiles.id': 1,
- created_at: -1,
- },
- {
- background: true,
- }
-);
+User.index({
+ lowercaseUsername: 1,
+ 'profiles.id': 1,
+ created_at: -1,
+});
// This query is executed often, to count the number of flagged accounts with
// usernames.
-User.index(
- {
- 'action_counts.flag': 1,
- 'status.username.status': 1,
- },
- {
- background: true,
- }
-);
+User.index({
+ 'action_counts.flag': 1,
+ 'status.username.status': 1,
+});
// Sorting users by created at is the default people search.
-User.index(
- {
- created_at: -1,
- },
- {
- background: true,
- }
-);
+User.index({
+ created_at: -1,
+});
/**
- * returns true if a commenter is staff
+ * returns true if a commenter is staff.
*/
User.method('isStaff', function() {
return this.role !== 'COMMENTER';
@@ -330,6 +318,9 @@ User.virtual('hasVerifiedEmail').get(function() {
});
});
+/**
+ * system returns true when the user is a system user.
+ */
User.virtual('system')
.get(function() {
return this._system;
diff --git a/package.json b/package.json
index f6ff46a59..97f4e373d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "talk",
- "version": "4.4.0",
+ "version": "4.4.1",
"description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net",
"main": "app.js",
"private": true,
@@ -219,6 +219,7 @@
"babel-plugin-dynamic-import-node": "^1.1.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
"browserstack-local": "^1.3.0",
+ "casual": "^1.5.19",
"chai": "^3.5.0",
"chai-as-promised": "^6.0.0",
"chai-datetime": "^1.5.0",
diff --git a/plugin-api/beta/server/getReactionConfig.js b/plugin-api/beta/server/getReactionConfig.js
index 5aa3063b3..f1f25b261 100644
--- a/plugin-api/beta/server/getReactionConfig.js
+++ b/plugin-api/beta/server/getReactionConfig.js
@@ -11,10 +11,22 @@ function getReactionConfig(reaction) {
if (CREATE_MONGO_INDEXES) {
// Create the index on the comment model based on the reaction config.
- Comment.collection.createIndex(
+ Comment.collection.ensureIndex(
{
- created_at: 1,
- [`action_counts.${sc(reaction)}`]: 1,
+ asset_id: 1,
+ [`action_counts.${sc(reaction)}`]: -1,
+ created_at: -1,
+ },
+ {
+ background: true,
+ }
+ );
+
+ Comment.collection.ensureIndex(
+ {
+ asset_id: 1,
+ [`action_counts.${sc(reaction)}`]: -1,
+ created_at: -1,
},
{
background: true,
diff --git a/plugins/talk-plugin-akismet/server/hooks.js b/plugins/talk-plugin-akismet/server/hooks.js
index 80a233584..b06557783 100644
--- a/plugins/talk-plugin-akismet/server/hooks.js
+++ b/plugins/talk-plugin-akismet/server/hooks.js
@@ -71,7 +71,7 @@ module.exports = {
permalink: asset.url,
comment_type: 'comment',
comment_content: input.body,
- is_test: true,
+ is_test: false,
});
debug(`comment analyzed as ${spam ? 'being' : 'not being'} spam`);
diff --git a/plugins/talk-plugin-local-auth/client/actions.js b/plugins/talk-plugin-local-auth/client/actions.js
new file mode 100644
index 000000000..972e1f6fd
--- /dev/null
+++ b/plugins/talk-plugin-local-auth/client/actions.js
@@ -0,0 +1,9 @@
+import * as actions from './constants';
+
+export const startAttach = () => ({
+ type: actions.START_ATTACH,
+});
+
+export const finishAttach = () => ({
+ type: actions.FINISH_ATTACH,
+});
diff --git a/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js b/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js
index dc7e0d974..a5bfd7096 100644
--- a/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js
+++ b/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js
@@ -45,6 +45,10 @@ class AddEmailAddressDialog extends React.Component {
),
};
+ componentDidMount() {
+ this.props.startAttach();
+ }
+
onChange = e => {
const { name, value } = e.target;
this.setState(
@@ -99,7 +103,13 @@ class AddEmailAddressDialog extends React.Component {
});
};
- confirmChanges = async () => {
+ finish = () => {
+ this.props.finishAttach();
+ };
+
+ confirmChanges = async e => {
+ e.preventDefault();
+
if (!this.validate()) {
this.showErrors();
return;
@@ -113,7 +123,11 @@ class AddEmailAddressDialog extends React.Component {
email: emailAddress,
password: confirmPassword,
});
- this.props.notify('success', 'Email Added!');
+
+ this.props.notify(
+ 'success',
+ t('talk-plugin-local-auth.add_email.added.alert')
+ );
this.goToNextStep();
} catch (err) {
this.props.notify('error', getErrorMessages(err));
@@ -143,13 +157,13 @@ class AddEmailAddressDialog extends React.Component {
)}
{step === 1 &&
!settings.requireEmailConfirmation && (
- {}} />
+
)}
{step === 1 &&
settings.requireEmailConfirmation && (
{}}
+ done={this.finish}
/>
)}
@@ -161,6 +175,8 @@ AddEmailAddressDialog.propTypes = {
attachLocalAuth: PropTypes.func.isRequired,
notify: PropTypes.func.isRequired,
root: PropTypes.object.isRequired,
+ startAttach: PropTypes.func.isRequired,
+ finishAttach: PropTypes.func.isRequired,
};
export default AddEmailAddressDialog;
diff --git a/plugins/talk-plugin-local-auth/client/components/AddEmailContent.js b/plugins/talk-plugin-local-auth/client/components/AddEmailContent.js
index 8d3fc308f..de296c812 100644
--- a/plugins/talk-plugin-local-auth/client/components/AddEmailContent.js
+++ b/plugins/talk-plugin-local-auth/client/components/AddEmailContent.js
@@ -41,7 +41,7 @@ const AddEmailContent = ({
-
diff --git a/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js b/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js
index f29dd8d86..f671c065b 100644
--- a/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js
+++ b/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js
@@ -4,10 +4,74 @@ import styles from './ChangeEmailContentDialog.css';
import InputField from './InputField';
import { Button } from 'plugin-api/beta/client/components/ui';
import { t } from 'plugin-api/beta/client/services';
+import validate from 'coral-framework/helpers/validate';
+import errorMsj from 'coral-framework/helpers/error';
+
+const initialState = {
+ showError: false,
+ formData: {
+ confirmPassword: '',
+ },
+ errors: {},
+};
class ChangeEmailContentDialog extends React.Component {
- state = {
- showError: false,
+ state = initialState;
+
+ clearForm = () => {
+ this.setState(initialState);
+ };
+
+ addError = err => {
+ this.setState(({ errors }) => ({
+ errors: { ...errors, ...err },
+ }));
+ };
+
+ removeError = errKey => {
+ this.setState(state => {
+ const { [errKey]: _, ...errors } = state.errors;
+ return {
+ errors,
+ };
+ });
+ };
+
+ fieldValidation = (value, type, name) => {
+ if (!value.length) {
+ this.addError({
+ [name]: t('talk-plugin-local-auth.change_password.required_field'),
+ });
+ } else if (!validate[type](value)) {
+ this.addError({ [name]: errorMsj[type] });
+ } else {
+ this.removeError(name);
+ }
+ };
+
+ onChange = e => {
+ const { name, value, type, dataset } = e.target;
+ const validationType = dataset.validationType || type;
+
+ this.setState(
+ state => ({
+ formData: {
+ ...state.formData,
+ [name]: value,
+ },
+ }),
+ () => {
+ this.fieldValidation(value, validationType, name);
+ }
+ );
+ };
+
+ hasError = err => {
+ return Object.keys(this.state.errors).indexOf(err) !== -1;
+ };
+
+ getError = errorKey => {
+ return this.state.errors[errorKey];
};
showError = () => {
@@ -16,24 +80,31 @@ class ChangeEmailContentDialog extends React.Component {
});
};
+ cancel = () => {
+ this.clearForm();
+ this.props.closeDialog();
+ };
+
confirmChanges = async e => {
e.preventDefault();
+ const { confirmPassword = '' } = this.state.formData;
+
if (this.formHasError()) {
this.showError();
return;
}
- await this.props.save();
+ await this.props.save(confirmPassword);
this.props.next();
};
- formHasError = () => this.props.hasError('confirmPassword');
+ formHasError = () => this.hasError('confirmPassword');
render() {
return (
-
+
×
@@ -59,17 +130,17 @@ class ChangeEmailContentDialog extends React.Component {
label={t('talk-plugin-local-auth.change_email.enter_password')}
name="confirmPassword"
type="password"
- onChange={this.props.onChange}
- defaultValue=""
- hasError={this.props.hasError('confirmPassword')}
- errorMsg={this.props.getError('confirmPassword')}
+ onChange={this.onChange}
+ value={this.state.formData.confirmPassword}
+ hasError={this.hasError('confirmPassword')}
+ errorMsg={this.getError('confirmPassword')}
showError={this.state.showError}
columnDisplay
/>