Merge branch 'master' into gdpr-email

This commit is contained in:
Kim Gardner
2018-05-01 17:50:27 -04:00
committed by GitHub
14 changed files with 578 additions and 55 deletions
@@ -84,7 +84,6 @@ class OrganizationSettings extends React.Component {
render() {
const { settings, slotPassthrough, canSave } = this.props;
const hasErrors = this.state.errors.length;
return (
<ConfigurePage title={t('configure.organization_information')}>
<p>{t('configure.organization_info_copy')}</p>
@@ -125,7 +124,7 @@ class OrganizationSettings extends React.Component {
[styles.editable]: this.state.editing,
})}
onChange={this.updateEmail}
value={settings.organizationContactEmail}
value={settings.organizationContactEmail || ''}
id={t('configure.organization_contact_email')}
readOnly={!this.state.editing}
/>
@@ -20,7 +20,8 @@ import OrganizationSettings from './OrganizationSettings';
import { withRouter } from 'react-router';
class ConfigureContainer extends React.Component {
state = { nextRoute: '' };
nextRoute = '';
unregisterLeaveHook = null;
savePending = async () => {
await this.props.updateSettings(this.props.pending);
@@ -40,18 +41,16 @@ class ConfigureContainer extends React.Component {
};
gotoNextRoute = () => {
const { nextRoute } = this.state;
if (nextRoute) {
this.props.router.push(nextRoute);
this.setState({ nextRoute: '' });
if (this.nextRoute) {
this.props.router.push(this.nextRoute);
this.nextRoute = '';
}
};
handleSectionChange = async section => {
const nextRoute = `/admin/configure/${section}`;
if (this.shouldShowSaveDialog()) {
await this.setState({ nextRoute });
if (this.hasPendingData()) {
this.nextRoute = nextRoute;
this.props.showSaveDialog();
} else {
// Just go to the section
@@ -59,20 +58,37 @@ class ConfigureContainer extends React.Component {
}
};
shouldShowSaveDialog = () => {
navigationPrompt = e => {
if (this.hasPendingData()) {
const confirmationMessage = 'Changes that you made may not be saved.';
e.returnValue = confirmationMessage; // Gecko, Trident, Chrome 34+
return confirmationMessage; // Gecko, WebKit, Chrome <34
}
};
hasPendingData = () => {
return !!Object.keys(this.props.pending).length;
};
routeLeave = ({ pathname }) => {
if (this.shouldShowSaveDialog()) {
this.setState({ nextRoute: pathname });
if (this.hasPendingData()) {
this.nextRoute = pathname;
this.props.showSaveDialog();
return false;
}
};
componentDidMount() {
this.props.router.setRouteLeaveHook(this.props.route, this.routeLeave);
this.unregisterLeaveHook = this.props.router.setRouteLeaveHook(
this.props.route,
this.routeLeave
);
window.addEventListener('beforeunload', this.navigationPrompt);
}
componentWillUnmount() {
this.unregisterLeaveHook();
window.removeEventListener('beforeunload', this.navigationPrompt);
}
render() {
+5 -1
View File
@@ -237,7 +237,11 @@ export function getTotalReactionsCount(actionSummaries) {
// Like lodash merge but does not recurse into arrays.
export function mergeExcludingArrays(objValue, srcValue) {
if (typeof srcValue === 'object' && !Array.isArray(srcValue)) {
if (
typeof srcValue === 'object' &&
!Array.isArray(srcValue) &&
srcValue !== null
) {
return assignWith({}, objValue, srcValue, mergeExcludingArrays);
}
return srcValue;
+1 -1
View File
@@ -250,7 +250,7 @@ en:
ALREADY_EXISTS: "Resource already exists"
INVALID_ASSET_URL: "Assert URL is invalid"
CANNOT_IGNORE_STAFF: "Cannot ignore Staff members."
email: "Not a valid E-Mail"
email: "Please enter a valid email."
confirm_password: "Passwords don't match. Please check again"
network_error: "Failed to connect to server. Check your internet connection and try again."
email_not_verified: "Email address {0} not verified."
+1 -14
View File
@@ -1,5 +1,3 @@
const { CREATE_MONGO_INDEXES } = require('../../config');
const Action = require('./action');
const Asset = require('./asset');
const Comment = require('./comment');
@@ -7,15 +5,4 @@ const Migration = require('./migration');
const Setting = require('./setting');
const User = require('./user');
const schema = { Action, Asset, Comment, Migration, Setting, User };
// Provide the schema to each of the plugins so that they can add in indexes if
// it is enabled.
if (CREATE_MONGO_INDEXES) {
const plugins = require('../../services/plugins');
plugins.get('server', 'indexes').map(({ indexes }) => {
indexes(schema);
});
}
module.exports = schema;
module.exports = { Action, Asset, Comment, Migration, Setting, User };
+14 -24
View File
@@ -2,23 +2,24 @@ const { SEARCH_OTHER_USERS } = require('../../../perms/constants');
const { ErrNotFound, ErrAlreadyExists } = require('../../../errors');
const pluralize = require('pluralize');
const sc = require('snake-case');
// const { CREATE_MONGO_INDEXES } = require('../../../config');
const { CREATE_MONGO_INDEXES } = require('../../../config');
const Comment = require('models/comment');
function getReactionConfig(reaction) {
reaction = reaction.toLowerCase();
// if (CREATE_MONGO_INDEXES) {
// // Create the index on the comment model based on the reaction config.
// CommentModel.collection.createIndex(
// {
// created_at: 1,
// [`action_counts.${sc(reaction)}`]: 1,
// },
// {
// background: true,
// }
// );
// }
if (CREATE_MONGO_INDEXES) {
// Create the index on the comment model based on the reaction config.
Comment.collection.createIndex(
{
created_at: 1,
[`action_counts.${sc(reaction)}`]: 1,
},
{
background: true,
}
);
}
const reactionPlural = pluralize(reaction);
const Reaction = reaction.charAt(0).toUpperCase() + reaction.slice(1);
@@ -127,17 +128,6 @@ function getReactionConfig(reaction) {
return {
typeDefs,
indexes: ({ Comment }) => {
Comment.index(
{
created_at: 1,
[`action_counts.${sc(reaction)}`]: 1,
},
{
background: true,
}
);
},
context: {
Sort: () => ({
Comments: {
+2
View File
@@ -6,6 +6,7 @@ import Login from './login/containers/Main';
import reducer from './login/reducer';
import ChangePassword from './profile-settings/containers/ChangePassword';
import Profile from './profile-settings/containers/Profile';
import ChangeUsername from './profile-settings/containers/ChangeUsername';
export default {
reducer,
@@ -14,6 +15,7 @@ export default {
stream: [UserBox, SignInButton, SetUsernameDialog],
login: [Login],
profileHeader: [Profile],
profileHeader: [ChangeUsername],
profileSettings: [ChangePassword],
},
};
@@ -0,0 +1,122 @@
.container {
margin-bottom: 20px;
display: flex;
position: relative;
color: #202020;
padding: 10px;
border-radius: 2px;
box-sizing: border-box;
justify-content: space-between;
&.editing {
background-color: #EDEDED;
}
}
.content {
flex-grow: 1;
}
.actions {
flex-grow: 0;
display: flex;
flex-direction: column;
align-items: center;
}
.email {
margin: 0;
}
.username {
margin-bottom: 4px;
}
.button {
border: 1px solid #787d80;
background-color: transparent;
height: 30px;
font-size: 0.9em;
line-height: normal;
}
.saveButton {
background-color: #3498DB;
border-color: #3498DB;
color: white;
> i {
font-size: 17px;
}
&:hover {
background-color: #399ee2;
color: white;
}
&:disabled {
border-color: #e0e0e0;
&:hover {
background-color: #e0e0e0;
color: #4f5c67;
cursor: default;
}
}
}
.cancelButton {
color:#787D80;
margin-top: 6px;
font-size: 0.9em;
&:hover {
cursor: pointer;
}
}
.detailLabel {
border: solid 1px #787D80;
border-radius: 2px;
background-color: white;
height: 30px;
display: inline-block;
width: 230px;
display: flex;
> .detailLabelIcon {
font-size: 1.2em;
padding: 0 5px;
color: #787D80;
line-height: 30px;
}
&.disabled {
background-color: #E0E0E0;
}
}
.detailValue {
background: transparent;
border: none;
font-size: 1em;
color: #000;
height: 30px;
outline: none;
flex: 1;
}
.bottomText {
color: #474747;
font-size: 0.9em;
}
.detailList {
list-style: none;
margin: 0;
padding: 0;
}
.detailItem {
margin-bottom: 12px;
}
@@ -0,0 +1,188 @@
import React from 'react';
import cn from 'classnames';
import PropTypes from 'prop-types';
import styles from './ChangeUsername.css';
import { Button } from 'plugin-api/beta/client/components/ui';
import ChangeUsernameDialog from './ChangeUsernameDialog';
import { t } from 'plugin-api/beta/client/services';
import InputField from './InputField';
import { getErrorMessages } from 'coral-framework/utils';
import { canUsernameBeUpdated } from 'coral-framework/utils/user';
const initialState = {
editing: false,
showDialog: false,
formData: {},
};
class ChangeUsername extends React.Component {
state = initialState;
clearForm = () => {
this.setState(initialState);
};
enableEditing = () => {
this.setState({
editing: true,
});
};
disableEditing = () => {
this.setState({
editing: false,
});
};
cancel = () => {
this.clearForm();
this.disableEditing();
};
showDialog = () => {
this.setState({
showDialog: true,
});
};
onSave = async () => {
this.showDialog();
};
saveChanges = async () => {
const { newUsername } = this.state.formData;
const { changeUsername } = this.props;
try {
await changeUsername(newUsername);
this.props.notify(
'success',
t('talk-plugin-auth.change_username.changed_username_success_msg')
);
} catch (err) {
this.props.notify('error', getErrorMessages(err));
}
this.clearForm();
this.disableEditing();
};
onChange = e => {
const { name, value } = e.target;
this.setState(state => ({
formData: {
...state.formData,
[name]: value,
},
}));
};
closeDialog = () => {
this.setState({
showDialog: false,
});
};
render() {
const {
username,
emailAddress,
root: { me: { state: { status } } },
notify,
} = this.props;
const { editing, formData, showDialog } = this.state;
return (
<section
className={cn('talk-plugin-auth--edit-profile', styles.container, {
[styles.editing]: editing,
})}
>
<ChangeUsernameDialog
canUsernameBeUpdated={canUsernameBeUpdated(status)}
showDialog={showDialog}
onChange={this.onChange}
formData={formData}
username={username}
closeDialog={this.closeDialog}
saveChanges={this.saveChanges}
notify={notify}
/>
{editing ? (
<div className={styles.content}>
<div className={styles.detailList}>
<InputField
icon="person"
id="newUsername"
name="newUsername"
onChange={this.onChange}
defaultValue={username}
columnDisplay
validationType="username"
>
<span className={styles.bottomText}>
{t('talk-plugin-auth.change_username.change_username_note')}
</span>
</InputField>
<InputField
icon="email"
id="email"
name="email"
value={emailAddress}
validationType="username"
disabled
/>
</div>
</div>
) : (
<div className={styles.content}>
<h2 className={styles.username}>{username}</h2>
{emailAddress ? (
<p className={styles.email}>{emailAddress}</p>
) : null}
</div>
)}
{editing ? (
<div className={styles.actions}>
<Button
className={cn(styles.button, styles.saveButton)}
icon="save"
onClick={this.onSave}
disabled={
!this.state.formData.newUsername ||
this.state.formData.newUsername === username
}
>
{t('talk-plugin-auth.change_username.save')}
</Button>
<a className={styles.cancelButton} onClick={this.cancel}>
{t('talk-plugin-auth.change_username.cancel')}
</a>
</div>
) : (
<div className={styles.actions}>
<Button
className={styles.button}
icon="settings"
onClick={this.enableEditing}
>
{t('talk-plugin-auth.change_username.edit_profile')}
</Button>
</div>
)}
</section>
);
}
}
ChangeUsername.propTypes = {
root: PropTypes.object.isRequired,
changeUsername: PropTypes.func.isRequired,
notify: PropTypes.func.isRequired,
username: PropTypes.string,
emailAddress: PropTypes.string,
};
export default ChangeUsername;
@@ -0,0 +1,84 @@
.dialog {
border: none;
box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2);
width: 320px;
top: 10px;
font-family: Helvetica, 'Helvetica Neue', Verdana, sans-serif;
font-size: 14px;
border-radius: 4px;
padding: 12px 20px;
}
.close {
font-size: 20px;
line-height: 14px;
top: 10px;
right: 10px;
position: absolute;
display: block;
font-weight: bold;
color: #363636;
cursor: pointer;
&:hover {
color: #6b6b6b;
}
}
.title {
font-size: 1.3em;
margin-bottom: 8px;
}
.description {
font-size: 1em;
line-height: 20px;
margin: 0;
}
.item {
display: block;
color: #4C4C4D;
font-size: 1em;
margin-bottom: 2px;
}
.bottomNote {
font-size: 0.9em;
line-height: 20px;
padding-top: 10px;
display: block;
}
.bottomActions {
text-align: right;
}
.usernamesChange {
margin: 18px 0;
}
.cancel {
border: 1px solid #787d80;
background-color: transparent;
height: 30px;
font-size: 0.9em;
line-height: normal;
&:hover {
background-color: #eaeaea;
}
}
.confirmChanges {
background-color: #3498DB;
border-color: #3498DB;
color: white;
height: 30px;
font-size: 0.9em;
&:hover {
background-color: #3ba3ec;
color: white;
}
}
@@ -0,0 +1,117 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import styles from './ChangeUsernameDialog.css';
import InputField from './InputField';
import { Button, Dialog } from 'plugin-api/beta/client/components/ui';
import { t } from 'plugin-api/beta/client/services';
class ChangeUsernameDialog extends React.Component {
state = {
showError: false,
};
showError = () => {
this.setState({
showError: true,
});
};
confirmChanges = async () => {
if (this.formHasError()) {
this.showError();
return;
}
if (!this.props.canUsernameBeUpdated) {
this.props.notify(
'error',
t('talk-plugin-auth.change_username.change_username_attempt')
);
return;
}
await this.props.saveChanges();
this.props.closeDialog();
};
formHasError = () =>
this.props.formData.confirmNewUsername !== this.props.formData.newUsername;
render() {
return (
<Dialog
open={this.props.showDialog}
className={cn(styles.dialog, 'talk-plugin-auth--edit-profile-dialog')}
>
<span className={styles.close} onClick={this.props.closeDialog}>
×
</span>
<h1 className={styles.title}>
{t('talk-plugin-auth.change_username.confirm_username_change')}
</h1>
<div className={styles.content}>
<p className={styles.description}>
{t('talk-plugin-auth.change_username.description')}
</p>
<div className={styles.usernamesChange}>
<span className={styles.item}>
{t('talk-plugin-auth.change_username.old_username')}:{' '}
{this.props.username}
</span>
<span className={styles.item}>
{t('talk-plugin-auth.change_username.new_username')}:{' '}
{this.props.formData.newUsername}
</span>
</div>
<form>
<InputField
id="confirmNewUsername"
label="Re-enter new username"
name="confirmNewUsername"
type="text"
onChange={this.props.onChange}
defaultValue=""
hasError={this.formHasError() && this.state.showError}
errorMsg={t(
'talk-plugin-auth.change_username.username_does_not_match'
)}
showError={this.state.showError}
columnDisplay
showSuccess={false}
validationType="username"
>
<span className={styles.bottomNote}>
{t('talk-plugin-auth.change_username.bottom_note')}
</span>
</InputField>
</form>
<div className={styles.bottomActions}>
<Button className={styles.cancel}>
{t('talk-plugin-auth.change_username.cancel')}
</Button>
<Button
className={styles.confirmChanges}
onClick={this.confirmChanges}
>
{t('talk-plugin-auth.change_username.confirm_changes')}
</Button>
</div>
</div>
</Dialog>
);
}
}
ChangeUsernameDialog.propTypes = {
saveChanges: PropTypes.func,
closeDialog: PropTypes.func,
showDialog: PropTypes.bool,
onChange: PropTypes.func,
username: PropTypes.string,
formData: PropTypes.object,
canUsernameBeUpdated: PropTypes.bool.isRequired,
notify: PropTypes.func.isRequired,
};
export default ChangeUsernameDialog;
@@ -0,0 +1,12 @@
import { compose } from 'react-apollo';
import { bindActionCreators } from 'redux';
import { connect } from 'plugin-api/beta/client/hocs';
import ChangeUsername from '../components/ChangeUsername';
import { notify } from 'coral-framework/actions/notification';
import { withChangeUsername } from 'plugin-api/beta/client/hocs';
const mapDispatchToProps = dispatch => bindActionCreators({ notify }, dispatch);
export default compose(connect(null, mapDispatchToProps), withChangeUsername)(
ChangeUsername
);
@@ -165,8 +165,9 @@ en:
incorrect_password: "Incorrect Password"
confirm_change: "Confirm Change"
cancel: "Cancel"
change_email_msg: "Email Address Changed - Your email address has been successfully changed. This email address will now be used for signing in and email notifications"
change_email_msg: "Email Address Changed - Your email address has been successfully changed. This email address will now be used for signing in and email notifications."
changed_username_success_msg: "Username Changed - Your username has been successfully changed. You will not be able to change your user name for 14 days."
change_username_attempt: "Username can't be updated. Usernames can only be changed every 14 days."
de:
talk-plugin-auth:
login:
+1
View File
@@ -66,6 +66,7 @@ if (CREATE_MONGO_INDEXES) {
require('../models/action');
require('../models/asset');
require('../models/comment');
require('../models/migration');
require('../models/setting');
require('../models/user');
require('./migration');