-
-
- {this.state.copied && 'Copied!'}
-
+
+ {t('configure.copy_and_paste')}
+
);
}
}
diff --git a/client/coral-admin/src/routes/Configure/components/ModerationSettings.js b/client/coral-admin/src/routes/Configure/components/ModerationSettings.js
index 92a5413e2..f5db719bb 100644
--- a/client/coral-admin/src/routes/Configure/components/ModerationSettings.js
+++ b/client/coral-admin/src/routes/Configure/components/ModerationSettings.js
@@ -1,90 +1,90 @@
import React from 'react';
import PropTypes from 'prop-types';
-import styles from './Configure.css';
-import {Card} from 'coral-ui';
-import {Checkbox} from 'react-mdl';
import Wordlist from './Wordlist';
+import Slot from 'coral-framework/components/Slot';
import t from 'coral-framework/services/i18n';
+import ConfigurePage from './ConfigurePage';
+import ConfigureCard from 'coral-framework/components/ConfigureCard';
-const updateModeration = (updateSettings, mod) => () => {
- const moderation = mod === 'PRE' ? 'POST' : 'PRE';
- updateSettings({moderation});
-};
+class ModerationSettings extends React.Component {
-const updateEmailConfirmation = (updateSettings, verify) => () => {
- updateSettings({requireEmailConfirmation: !verify});
-};
+ updateModeration = () => {
+ const updater = {moderation: {$set: this.props.settings.moderation === 'PRE' ? 'POST' : 'PRE'}};
+ this.props.updatePending({updater});
+ };
-const updatePremodLinksEnable = (updateSettings, premodLinks) => () => {
- const premodLinksEnable = !premodLinks;
- updateSettings({premodLinksEnable});
-};
+ updateEmailConfirmation = () => {
+ const updater = {requireEmailConfirmation: {$set: !this.props.settings.requireEmailConfirmation}};
+ this.props.updatePending({updater});
+ };
-const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => {
+ updatePremodLinksEnable = () => {
+ const updater = {premodLinksEnable: {$set: !this.props.settings.premodLinksEnable}};
+ this.props.updatePending({updater});
+ };
- // just putting this here for shorthand below
- const on = styles.enabledSetting;
- const off = styles.disabledSetting;
+ updateWordlist = (listName, list) => {
+ this.props.updatePending({updater: {
+ wordlist: {$apply: (wordlist) => {
+ const changeSet = {[listName]: list};
+ if (!wordlist) {
+ return changeSet;
+ }
+ return {
+ ...wordlist,
+ ...changeSet,
+ };
+ }},
+ }});
+ };
- return (
-
-
-
-
-
-
-
{t('configure.require_email_verification')}
-
- {t('configure.require_email_verification_text')}
-
-
-
-
-
-
-
-
-
{t('configure.enable_pre_moderation')}
-
- {t('configure.enable_pre_moderation_text')}
-
-
-
-
-
-
-
-
-
{t('configure.enable_premod_links')}
-
- {t('configure.enable_premod_links_text')}
-
-
-
-
-
- );
-};
+ render() {
+ const {settings, data, root} = this.props;
+
+ return (
+
+
+ {t('configure.require_email_verification_text')}
+
+
+ {t('configure.enable_pre_moderation_text')}
+
+
+ {t('configure.enable_premod_links_text')}
+
+
+
+
+ );
+ }
+}
ModerationSettings.propTypes = {
- onChangeWordlist: PropTypes.func.isRequired,
- settings: PropTypes.shape({
- moderation: PropTypes.string.isRequired,
- wordlist: PropTypes.shape({
- banned: PropTypes.array.isRequired,
- suspect: PropTypes.array.isRequired
- })
- }).isRequired,
- updateSettings: PropTypes.func.isRequired
+ updatePending: PropTypes.func.isRequired,
+ data: PropTypes.object.isRequired,
+ root: PropTypes.object.isRequired,
+ settings: PropTypes.object.isRequired,
};
export default ModerationSettings;
diff --git a/client/coral-admin/src/routes/Configure/components/StreamSettings.css b/client/coral-admin/src/routes/Configure/components/StreamSettings.css
new file mode 100644
index 000000000..5ddd46bd2
--- /dev/null
+++ b/client/coral-admin/src/routes/Configure/components/StreamSettings.css
@@ -0,0 +1,55 @@
+.configSettingInfoBox {
+ min-height: 100px;
+ margin-bottom: 20px;
+ width: auto;
+ height: auto;
+ text-align: left;
+ overflow: visible;
+}
+
+.descriptionBox {
+ margin-top: 15px;
+
+ input {
+ height: 150px;
+ }
+}
+
+.configTimeoutSelect {
+ display: inline-block;
+ margin-left: 20px;
+
+ i { /* fix for firefox and react-mdl-selectfield@0.2.0 */
+ padding: 20px 0;
+ vertical-align: top;
+ }
+}
+
+.hidden {
+ display: none;
+}
+
+.inlineTextfield {
+ border-color: #ccc;
+ border-style: solid;
+ border-width: 0px 0px 1px 0px;
+ text-align: center;
+ font-size: inherit;
+}
+
+.inlineTextfield:focus {
+ outline: none;
+}
+
+.charCountTexfield, .editCommentTimeframeTextfield {
+ width: 4em;
+ padding: 0px;
+}
+
+.charCountTexfieldEnabled {
+ border-color: #00796b;
+}
+
+
+
+
diff --git a/client/coral-admin/src/routes/Configure/components/StreamSettings.js b/client/coral-admin/src/routes/Configure/components/StreamSettings.js
index 1427f949e..9c05711cb 100644
--- a/client/coral-admin/src/routes/Configure/components/StreamSettings.js
+++ b/client/coral-admin/src/routes/Configure/components/StreamSettings.js
@@ -1,10 +1,15 @@
import React from 'react';
import {SelectField, Option} from 'react-mdl-selectfield';
import t from 'coral-framework/services/i18n';
-import styles from './Configure.css';
-import {Checkbox, Textfield} from 'react-mdl';
-import {Card, Icon, TextArea} from 'coral-ui';
+import styles from './StreamSettings.css';
+import {Textfield} from 'react-mdl';
+import {Icon, TextArea} from 'coral-ui';
+import PropTypes from 'prop-types';
+import Slot from 'coral-framework/components/Slot';
import MarkdownEditor from 'coral-framework/components/MarkdownEditor';
+import cn from 'classnames';
+import ConfigurePage from './ConfigurePage';
+import ConfigureCard from 'coral-framework/components/ConfigureCard';
const TIMESTAMPS = {
weeks: 60 * 60 * 24 * 7,
@@ -12,187 +17,6 @@ const TIMESTAMPS = {
hours: 60 * 60
};
-const updateCharCountEnable = (updateSettings, charCountChecked) => () => {
- const charCountEnable = !charCountChecked;
- updateSettings({charCountEnable});
-};
-
-const updateCharCount = (updateSettings, settingsError) => (event) => {
- const charCount = event.target.value;
- if (charCount.match(/[^0-9]/) || charCount.length === 0) {
- settingsError('charCount', true);
- } else {
- settingsError('charCount', false);
- }
- updateSettings({charCount: charCount});
-};
-
-const updateInfoBoxEnable = (updateSettings, infoBox) => () => {
- const infoBoxEnable = !infoBox;
- updateSettings({infoBoxEnable});
-};
-
-const updateInfoBoxContent = (updateSettings) => (value) => {
- const infoBoxContent = value;
- updateSettings({infoBoxContent});
-};
-
-const updateAutoClose = (updateSettings, autoCloseStream) => () => {
- updateSettings({autoCloseStream});
-};
-
-const updateClosedMessage = (updateSettings) => (event) => {
- const closedMessage = event.target.value;
- updateSettings({closedMessage});
-};
-
-// If we are changing the measure we need to recalculate using the old amount
-// Same thing if we are just changing the amount
-const updateClosedTimeout = (updateSettings, ts, isMeasure) => (event) => {
- if (isMeasure) {
- const amount = getTimeoutAmount(ts);
- const closedTimeout = amount * TIMESTAMPS[event];
- updateSettings({closedTimeout});
- } else {
- const val = event.target.value;
- const measure = getTimeoutMeasure(ts);
- const closedTimeout = val * TIMESTAMPS[measure];
- updateSettings({closedTimeout});
- }
-};
-
-const updateEditCommentWindowLength = (updateSettings) => (e) => {
- const value = e.target.value;
- const valueAsNumber = parseFloat(value);
- const milliseconds = (!isNaN(valueAsNumber)) && (valueAsNumber * 1000);
- updateSettings({editCommentWindowLength: milliseconds || value});
-};
-
-const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
-
- // just putting this here for shorthand below
- const on = styles.enabledSetting;
- const off = styles.disabledSetting;
-
- return (
-
-
-
-
-
-
-
{t('configure.comment_count_header')}
-
- {t('configure.comment_count_text_pre')}
-
- {t('configure.comment_count_text_post')}
- {
- errors.charCount &&
-
-
-
- {t('configure.comment_count_error')}
-
- }
-
-
-
-
-
-
-
-
-
- {t('configure.include_comment_stream')}
-
-
- {t('configure.include_comment_stream_desc')}
-
-
-
-
-
-
-
-
-
{t('configure.closed_stream_settings')}
-
{t('configure.closed_comments_desc')}
-
-
-
-
-
- {/* Edit Comment Timeframe */}
-
- {t('configure.edit_comment_timeframe_heading')}
-
- {t('configure.edit_comment_timeframe_text_pre')}
-
-
-
- {t('configure.edit_comment_timeframe_text_post')}
-
-
-
-
-
-
-
-
{t('configure.close_after')}
-
-
-
-
-
-
-
-
-
-
-
- {/* the above card should be the last one if at all possible because of z-index issues with the selects */}
-
- );
-};
-
-export default StreamSettings;
-
// To see if we are talking about weeks, days or hours
// We talk the remainder of the division and see if it's 0
const getTimeoutMeasure = (ts) => {
@@ -208,3 +32,190 @@ const getTimeoutMeasure = (ts) => {
// Dividing the amount by it's measure (hours, days, weeks) we
// obtain the amount of time
const getTimeoutAmount = (ts) => ts / TIMESTAMPS[getTimeoutMeasure(ts)];
+
+class StreamSettings extends React.Component {
+
+ updateCharCountEnable = () => {
+ const updater = {charCountEnable: {$set: !this.props.settings.charCountEnable}};
+ this.props.updatePending({updater});
+ };
+
+ updateCharCount = (event) => {
+ let error = null;
+ const charCount = event.target.value;
+ if (charCount.match(/[^0-9]/) || charCount.length === 0) {
+ error = true;
+ }
+
+ const updater = {charCount: {$set: charCount}};
+ const errorUpdater = {charCount: {$set: error}};
+
+ this.props.updatePending({updater, errorUpdater});
+ };
+
+ updateInfoBoxEnable = () => {
+ const updater = {infoBoxEnable: {$set: !this.props.settings.infoBoxEnable}};
+ this.props.updatePending({updater});
+ };
+
+ updateInfoBoxContent = (value) => {
+ const updater = {infoBoxContent: {$set: value}};
+ this.props.updatePending({updater});
+ };
+
+ updateClosedMessage = (event) => {
+ const updater = {closedMessage: {$set: event.target.value}};
+ this.props.updatePending({updater});
+ };
+
+ updateEditCommentWindowLength = (e) => {
+ const value = e.target.value;
+ const valueAsNumber = parseFloat(value);
+ const milliseconds = (!isNaN(valueAsNumber)) && (valueAsNumber * 1000);
+
+ const updater = {editCommentWindowLength: {$set: milliseconds || value}};
+ this.props.updatePending({updater});
+ };
+
+ updateAutoClose = () => {
+ const updater = {autoCloseStream: {$set: !this.props.settings.autoCloseStream}};
+ this.props.updatePending({updater});
+ };
+
+ updateClosedTimeout = (event) => {
+ const val = event.target.value;
+ const measure = getTimeoutMeasure(this.props.settings.closedTimeout);
+
+ const updater = {closedTimeout: {$set: val * TIMESTAMPS[measure]}};
+ this.props.updatePending({updater});
+ };
+
+ // If we are changing the measure we need to recalculate using the old amount
+ // Same thing if we are just changing the amount
+ updateClosedTimeoutMeasure = (event) => {
+ const amount = getTimeoutAmount(this.props.settings.closedTimeout);
+
+ const updater = {closedTimeout: {$set: amount * TIMESTAMPS[event]}};
+ this.props.updatePending({updater});
+ };
+
+ render() {
+ const {settings, data, root, errors} = this.props;
+
+ return (
+
+
+ {t('configure.comment_count_text_pre')}
+
+ {t('configure.comment_count_text_post')}
+ {
+ errors.charCount &&
+
+
+
+ {t('configure.comment_count_error')}
+
+ }
+
+
+
+ {t('configure.include_comment_stream_desc')}
+
+
+
+
+
+
+ {t('configure.closed_comments_desc')}
+
+
+
+
+
+ {t('configure.edit_comment_timeframe_text_pre')}
+
+
+
+ {t('configure.edit_comment_timeframe_text_post')}
+
+
+
+
+
+
+
+
+
+
+
+ {/* the above card should be the last one if at all possible because of z-index issues with the selects */}
+
+
+ );
+ }
+}
+
+StreamSettings.propTypes = {
+ updatePending: PropTypes.func.isRequired,
+ errors: PropTypes.object.isRequired,
+ data: PropTypes.object.isRequired,
+ root: PropTypes.object.isRequired,
+ settings: PropTypes.object.isRequired,
+};
+
+export default StreamSettings;
+
diff --git a/client/coral-admin/src/routes/Configure/components/TechSettings.css b/client/coral-admin/src/routes/Configure/components/TechSettings.css
new file mode 100644
index 000000000..e3d6a071c
--- /dev/null
+++ b/client/coral-admin/src/routes/Configure/components/TechSettings.css
@@ -0,0 +1,10 @@
+.customCSSInput {
+ width: 100%;
+ font-size: 14px;
+ padding: 14px;
+ letter-spacing: 0.03em;
+ color: #555;
+ box-sizing: border-box;
+}
+
+
diff --git a/client/coral-admin/src/routes/Configure/components/TechSettings.js b/client/coral-admin/src/routes/Configure/components/TechSettings.js
index 4f1202a67..7f16b6064 100644
--- a/client/coral-admin/src/routes/Configure/components/TechSettings.js
+++ b/client/coral-admin/src/routes/Configure/components/TechSettings.js
@@ -1,44 +1,67 @@
import React from 'react';
import PropTypes from 'prop-types';
-import {Card} from 'coral-ui';
import Domainlist from './Domainlist';
import EmbedLink from './EmbedLink';
-import styles from './Configure.css';
+import styles from './TechSettings.css';
+import Slot from 'coral-framework/components/Slot';
import t from 'coral-framework/services/i18n';
+import ConfigurePage from './ConfigurePage';
+import ConfigureCard from 'coral-framework/components/ConfigureCard';
-const updateCustomCssUrl = (updateSettings) => (event) => {
- const customCssUrl = event.target.value;
- updateSettings({customCssUrl});
-};
+class TechSettings extends React.Component {
-const TechSettings = ({settings, onChangeDomainlist, updateSettings}) => {
- return (
-
-
-
-
-
-
{t('configure.custom_css_url')}
+ updateCustomCssUrl = (event) => {
+ const updater = {customCssUrl: {$set: event.target.value}};
+ this.props.updatePending({updater});
+ };
+
+ updateDomainlist = (listName, list) => {
+ this.props.updatePending({updater: {
+ domains: {$apply: (domains) => {
+ const changeSet = {[listName]: list};
+ if (!domains) {
+ return changeSet;
+ }
+ return {
+ ...domains,
+ ...changeSet,
+ };
+ }},
+ }});
+ };
+
+ render() {
+ const {settings, data, root} = this.props;
+ return (
+
+
+
+
{t('configure.custom_css_url_desc')}
-
-
-
- );
-};
+ onChange={this.updateCustomCssUrl} />
+
+
+
+ );
+ }
+}
TechSettings.propTypes = {
- settings: PropTypes.shape({
- domains: PropTypes.shape({
- whitelist: PropTypes.array.isRequired
- })
- }).isRequired,
- updateSettings: PropTypes.func.isRequired
+ updatePending: PropTypes.func.isRequired,
+ data: PropTypes.object.isRequired,
+ root: PropTypes.object.isRequired,
+ settings: PropTypes.object.isRequired,
};
export default TechSettings;
diff --git a/client/coral-admin/src/routes/Configure/components/Wordlist.js b/client/coral-admin/src/routes/Configure/components/Wordlist.js
index e96f37f9f..34a6ed825 100644
--- a/client/coral-admin/src/routes/Configure/components/Wordlist.js
+++ b/client/coral-admin/src/routes/Configure/components/Wordlist.js
@@ -1,33 +1,33 @@
import React from 'react';
import t from 'coral-framework/services/i18n';
import TagsInput from 'coral-admin/src/components/TagsInput';
-import styles from './Configure.css';
-import {Card} from 'coral-ui';
+import PropTypes from 'prop-types';
+import ConfigureCard from 'coral-framework/components/ConfigureCard';
const Wordlist = ({suspectWords, bannedWords, onChangeWordlist}) => (
-
- {t('configure.banned_words_title')}
- {t('configure.banned_word_text')}
-
- onChangeWordlist('banned', tags)}
- />
-
-
-
- {t('configure.suspect_word_title')}
- {t('configure.suspect_word_text')}
-
- onChangeWordlist('suspect', tags)} />
-
-
+
+ {t('configure.banned_word_text')}
+ onChangeWordlist('banned', tags)}
+ />
+
+
+ {t('configure.suspect_word_text')}
+ onChangeWordlist('suspect', tags)} />
+
);
+Wordlist.propTypes = {
+ suspectWords: PropTypes.array.isRequired,
+ bannedWords: PropTypes.array.isRequired,
+ onChangeWordlist: PropTypes.func.isRequired,
+};
+
export default Wordlist;
diff --git a/client/coral-admin/src/routes/Configure/containers/Configure.js b/client/coral-admin/src/routes/Configure/containers/Configure.js
index aff1e57b2..8f80a3e24 100644
--- a/client/coral-admin/src/routes/Configure/containers/Configure.js
+++ b/client/coral-admin/src/routes/Configure/containers/Configure.js
@@ -1,42 +1,128 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
-import {compose} from 'react-apollo';
-import {
- fetchSettings,
- updateSettings,
- saveSettingsToServer,
- updateWordlist,
- updateDomainlist
-} from '../../../actions/settings';
+import {compose, gql} from 'react-apollo';
+import withQuery from 'coral-framework/hocs/withQuery';
+import {Spinner} from 'coral-ui';
+import {notify} from 'coral-framework/actions/notification';
+import PropTypes from 'prop-types';
+import assignWith from 'lodash/assignWith';
+import {withUpdateSettings} from 'coral-framework/graphql/mutations';
+import {getErrorMessages, getDefinitionName} from 'coral-framework/utils';
+import StreamSettings from './StreamSettings';
+import TechSettings from './TechSettings';
+import ModerationSettings from './ModerationSettings';
+import {clearPending, setActiveSection} from '../../../actions/configure';
import Configure from '../components/Configure';
+// Like lodash merge but does not recurse into arrays.
+const mergeExcludingArrays = (objValue, srcValue) => {
+ if (typeof srcValue === 'object' && !Array.isArray(srcValue)) {
+ return assignWith({}, objValue, srcValue, mergeExcludingArrays);
+ }
+ return srcValue;
+};
+
class ConfigureContainer extends Component {
- componentWillMount = () => {
- this.props.fetchSettings();
+
+ // Merge current settings with pending settings.
+ getMergedSettings = (props = this.props) => {
+ return assignWith({}, props.root.settings, props.pending, mergeExcludingArrays);
+ }
+
+ // Cached merged settings.
+ mergedSettings = this.getMergedSettings();
+
+ savePending = async () => {
+ try {
+ await this.props.updateSettings(this.props.pending);
+ this.props.clearPending();
+ }
+ catch(err) {
+ this.props.notify('error', getErrorMessages(err));
+ }
+ };
+
+ componentWillReceiveProps(nextProps) {
+
+ // Recalculate merged settings when necessary.
+ if (this.props.root.settings !== nextProps.root.settings || this.props.pending !== nextProps.pending) {
+ this.mergedSettings = this.getMergedSettings(nextProps);
+ }
}
render () {
- return
;
+ if(this.props.data.loading) {
+ return
;
+ }
+
+ return
;
}
}
+const withConfigureQuery = withQuery(gql`
+ query TalkAdmin_Configure {
+ settings {
+ ...${getDefinitionName(StreamSettings.fragments.settings)}
+ ...${getDefinitionName(TechSettings.fragments.settings)}
+ ...${getDefinitionName(ModerationSettings.fragments.settings)}
+ }
+ ...${getDefinitionName(StreamSettings.fragments.root)}
+ ...${getDefinitionName(TechSettings.fragments.root)}
+ ...${getDefinitionName(ModerationSettings.fragments.root)}
+ }
+ ${StreamSettings.fragments.root}
+ ${StreamSettings.fragments.settings}
+ ${TechSettings.fragments.root}
+ ${TechSettings.fragments.settings}
+ ${ModerationSettings.fragments.root}
+ ${ModerationSettings.fragments.settings}
+ `, {
+ options: () => ({
+ variables: {},
+ }),
+});
+
const mapStateToProps = (state) => ({
auth: state.auth,
- settings: state.settings
+ pending: state.configure.pending,
+ canSave: state.configure.canSave,
+ activeSection: state.configure.activeSection,
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
- fetchSettings,
- updateSettings,
- saveSettingsToServer,
- updateWordlist,
- updateDomainlist
+ notify,
+ clearPending,
+ setActiveSection,
}, dispatch);
export default compose(
+ withUpdateSettings,
+ withConfigureQuery,
connect(mapStateToProps, mapDispatchToProps),
)(ConfigureContainer);
+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,
+ canSave: PropTypes.bool.isRequired,
+ pending: PropTypes.object.isRequired,
+ activeSection: PropTypes.string.isRequired,
+};
diff --git a/client/coral-admin/src/routes/Configure/containers/ModerationSettings.js b/client/coral-admin/src/routes/Configure/containers/ModerationSettings.js
new file mode 100644
index 000000000..0583378a9
--- /dev/null
+++ b/client/coral-admin/src/routes/Configure/containers/ModerationSettings.js
@@ -0,0 +1,40 @@
+import {connect} from 'react-redux';
+import {bindActionCreators} from 'redux';
+import {compose, gql} from 'react-apollo';
+import ModerationSettings from '../components/ModerationSettings';
+import withFragments from 'coral-framework/hocs/withFragments';
+import {getSlotFragmentSpreads} from 'coral-framework/utils';
+import {updatePending} from '../../../actions/configure';
+
+const slots = [
+ 'adminModerationSettings',
+];
+
+const mapDispatchToProps = (dispatch) =>
+ bindActionCreators({
+ updatePending,
+ }, dispatch);
+
+export default compose(
+ withFragments({
+ root: gql`
+ fragment TalkAdmin_ModerationSettings_root on RootQuery {
+ __typename
+ ${getSlotFragmentSpreads(slots, 'root')}
+ }
+ `,
+ settings: gql`
+ fragment TalkAdmin_ModerationSettings_settings on Settings {
+ requireEmailConfirmation
+ moderation
+ premodLinksEnable
+ wordlist {
+ suspect
+ banned
+ }
+ ${getSlotFragmentSpreads(slots, 'settings')}
+ }
+ `
+ }),
+ connect(null, mapDispatchToProps),
+)(ModerationSettings);
diff --git a/client/coral-admin/src/routes/Configure/containers/StreamSettings.js b/client/coral-admin/src/routes/Configure/containers/StreamSettings.js
new file mode 100644
index 000000000..9a7f79fa6
--- /dev/null
+++ b/client/coral-admin/src/routes/Configure/containers/StreamSettings.js
@@ -0,0 +1,45 @@
+import {connect} from 'react-redux';
+import {bindActionCreators} from 'redux';
+import {compose, gql} from 'react-apollo';
+import StreamSettings from '../components/StreamSettings';
+import withFragments from 'coral-framework/hocs/withFragments';
+import {getSlotFragmentSpreads} from 'coral-framework/utils';
+import {updatePending} from '../../../actions/configure';
+
+const slots = [
+ 'adminStreamSettings',
+];
+
+const mapStateToProps = (state) => ({
+ errors: state.configure.errors,
+});
+
+const mapDispatchToProps = (dispatch) =>
+ bindActionCreators({
+ updatePending,
+ }, dispatch);
+
+export default compose(
+ withFragments({
+ root: gql`
+ fragment TalkAdmin_StreamSettings_root on RootQuery {
+ __typename
+ ${getSlotFragmentSpreads(slots, 'root')}
+ }
+ `,
+ settings: gql`
+ fragment TalkAdmin_StreamSettings_settings on Settings {
+ infoBoxEnable
+ charCount
+ charCountEnable
+ infoBoxContent
+ editCommentWindowLength
+ autoCloseStream
+ closedTimeout
+ closedMessage
+ ${getSlotFragmentSpreads(slots, 'settings')}
+ }
+ `
+ }),
+ connect(mapStateToProps, mapDispatchToProps),
+)(StreamSettings);
diff --git a/client/coral-admin/src/routes/Configure/containers/TechSettings.js b/client/coral-admin/src/routes/Configure/containers/TechSettings.js
new file mode 100644
index 000000000..df33a3f9a
--- /dev/null
+++ b/client/coral-admin/src/routes/Configure/containers/TechSettings.js
@@ -0,0 +1,37 @@
+import {connect} from 'react-redux';
+import {bindActionCreators} from 'redux';
+import {compose, gql} from 'react-apollo';
+import TechSettings from '../components/TechSettings';
+import withFragments from 'coral-framework/hocs/withFragments';
+import {getSlotFragmentSpreads} from 'coral-framework/utils';
+import {updatePending} from '../../../actions/configure';
+
+const slots = [
+ 'adminTechSettings',
+];
+
+const mapDispatchToProps = (dispatch) =>
+ bindActionCreators({
+ updatePending,
+ }, dispatch);
+
+export default compose(
+ withFragments({
+ root: gql`
+ fragment TalkAdmin_TechSettings_root on RootQuery {
+ __typename
+ ${getSlotFragmentSpreads(slots, 'root')}
+ }
+ `,
+ settings: gql`
+ fragment TalkAdmin_TechSettings_settings on Settings {
+ customCssUrl
+ domains {
+ whitelist
+ }
+ ${getSlotFragmentSpreads(slots, 'settings')}
+ }
+ `
+ }),
+ connect(null, mapDispatchToProps),
+)(TechSettings);
diff --git a/client/coral-admin/src/routes/Dashboard/components/Dashboard.css b/client/coral-admin/src/routes/Dashboard/components/Dashboard.css
index 586ff6c08..aee7e702b 100644
--- a/client/coral-admin/src/routes/Dashboard/components/Dashboard.css
+++ b/client/coral-admin/src/routes/Dashboard/components/Dashboard.css
@@ -4,6 +4,8 @@
.Dashboard {
display: flex;
+ max-width: 1280px;
+ margin: 0 auto;
}
.heading {
diff --git a/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js b/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js
index 2bb192f2e..e2e9ef28c 100644
--- a/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js
+++ b/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js
@@ -42,11 +42,11 @@ export const witDashboardQuery = withQuery(gql`
}
}
`, {
- options: ({settings: {dashboardWindowStart, dashboardWindowEnd}}) => {
+ options: ({windowStart, windowEnd}) => {
return {
variables: {
- from: dashboardWindowStart,
- to: dashboardWindowEnd
+ from: windowStart,
+ to: windowEnd,
}
};
}
@@ -54,8 +54,8 @@ export const witDashboardQuery = withQuery(gql`
const mapStateToProps = (state) => {
return {
- settings: state.settings,
- moderation: state.moderation
+ windowStart: state.dashboard.windowStart,
+ windowEnd: state.dashboard.windowEnd,
};
};
diff --git a/client/coral-admin/src/routes/Install/components/Steps/AddOrganizationName.js b/client/coral-admin/src/routes/Install/components/Steps/AddOrganizationName.js
index 064167dda..2d2743757 100644
--- a/client/coral-admin/src/routes/Install/components/Steps/AddOrganizationName.js
+++ b/client/coral-admin/src/routes/Install/components/Steps/AddOrganizationName.js
@@ -1,13 +1,14 @@
import React from 'react';
import styles from './style.css';
import {TextField, Button} from 'coral-ui';
-
+import PropTypes from 'prop-types';
import t from 'coral-framework/services/i18n';
+import cn from 'classnames';
const AddOrganizationName = (props) => {
const {handleSettingsChange, handleSettingsSubmit, install} = props;
return (
-
+
{t('install.add_organization.description')}
);
};
+AddOrganizationName.propTypes = {
+ handleSettingsChange: PropTypes.func,
+ handleSettingsSubmit: PropTypes.func,
+ install: PropTypes.object,
+};
+
export default AddOrganizationName;
diff --git a/client/coral-admin/src/routes/Install/components/Steps/CreateYourAccount.js b/client/coral-admin/src/routes/Install/components/Steps/CreateYourAccount.js
index d4d43dc92..dec111c5b 100644
--- a/client/coral-admin/src/routes/Install/components/Steps/CreateYourAccount.js
+++ b/client/coral-admin/src/routes/Install/components/Steps/CreateYourAccount.js
@@ -1,13 +1,14 @@
import React from 'react';
import styles from './style.css';
import {TextField, Button, Spinner} from 'coral-ui';
-
+import PropTypes from 'prop-types';
import t from 'coral-framework/services/i18n';
+import cn from 'classnames';
const InitialStep = (props) => {
const {handleUserChange, handleUserSubmit, install} = props;
return (
-