diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js index 28255aeb5..360ceaa93 100644 --- a/client/coral-admin/src/AppRouter.js +++ b/client/coral-admin/src/AppRouter.js @@ -10,6 +10,7 @@ import Configure from 'routes/Configure'; import StreamSettings from './routes/Configure/containers/StreamSettings'; import ModerationSettings from './routes/Configure/containers/ModerationSettings'; import TechSettings from './routes/Configure/containers/TechSettings'; +import OrganizationSettings from './routes/Configure/containers/OrganizationSettings'; import { ModerationLayout, Moderation } from 'routes/Moderation'; @@ -25,6 +26,7 @@ const routes = ( + diff --git a/client/coral-admin/src/reducers/install.js b/client/coral-admin/src/reducers/install.js index c0a694977..8d96b9b93 100644 --- a/client/coral-admin/src/reducers/install.js +++ b/client/coral-admin/src/reducers/install.js @@ -5,6 +5,7 @@ const initialState = { isLoading: false, data: { settings: { + organizationContactEmail: '', organizationName: '', domains: { whitelist: [], @@ -19,6 +20,7 @@ const initialState = { }, errors: { organizationName: '', + organizationContactEmail: '', username: '', email: '', password: '', diff --git a/client/coral-admin/src/routes/Configure/components/Configure.js b/client/coral-admin/src/routes/Configure/components/Configure.js index 4d88f8420..bcd48c21c 100644 --- a/client/coral-admin/src/routes/Configure/components/Configure.js +++ b/client/coral-admin/src/routes/Configure/components/Configure.js @@ -8,7 +8,14 @@ import SaveChangesDialog from './SaveChangesDialog'; class Configure extends React.Component { render() { - const { canSave, currentUser, root, savePending, settings } = this.props; + const { + canSave, + currentUser, + root, + savePending, + settings, + clearPending, + } = this.props; if (!can(currentUser, 'UPDATE_CONFIG')) { return

{t('configure.access_message')}

; @@ -17,6 +24,9 @@ class Configure extends React.Component { const passProps = { root, settings, + savePending, + clearPending, + canSave, }; return ( @@ -41,6 +51,9 @@ class Configure extends React.Component { {t('configure.tech_settings')} + + {t('configure.organization_information')} +
{canSave ? ( @@ -81,6 +94,7 @@ Configure.propTypes = { children: PropTypes.node.isRequired, saveDialog: PropTypes.bool, hideSaveDialog: PropTypes.func.isRequired, + clearPending: PropTypes.func.isRequired, }; export default Configure; diff --git a/client/coral-admin/src/routes/Configure/components/OrganizationSettings.css b/client/coral-admin/src/routes/Configure/components/OrganizationSettings.css new file mode 100644 index 000000000..2468496b7 --- /dev/null +++ b/client/coral-admin/src/routes/Configure/components/OrganizationSettings.css @@ -0,0 +1,87 @@ +.label { + display: block; +} + +.detailList { + padding: 0; + margin: 0; +} + +.detailLabel { + color: #000; + font-size: 1.1em; + font-weight: bold; + display: block; + margin-bottom: 4px; +} + +.detailValue { + padding: 6px 0; + border: solid 1px transparent; + display: block; + font-size: 1.1em; + border-radius: 2px; + color: #424242; + box-sizing: border-box; + min-width: 300px; +} + +.editable { + padding-left: 10px; + padding-right: 10px; + border-color: grey; +} + +.detailItem { + margin-bottom: 16px; + list-style: none; +} + +.button, .button:disabled { + background: white; + border: solid 1px grey; +} + +.actionBox { + flex-grow: 0; +} + +.cancelButton { + padding: 10px; + display: block; + color: #4f5c67; + font-weight: 500; + + &:hover { + cursor: pointer; + } +} + +.changedSave { + background-color: #00796B; + color: white; +} + +.errorList { + list-style: none; + padding: 0; + margin: 0; +} + +.errorItem { + padding: 5px 10px; + margin-bottom: 20px; + color: #b71c1c; + border-radius: 2px; + display: inline-block; + background: #F9D3CE; +} + +.container { + display: flex; +} + +.content { + flex-grow: 1; + padding-right: 40px; +} \ No newline at end of file diff --git a/client/coral-admin/src/routes/Configure/components/OrganizationSettings.js b/client/coral-admin/src/routes/Configure/components/OrganizationSettings.js new file mode 100644 index 000000000..38f211781 --- /dev/null +++ b/client/coral-admin/src/routes/Configure/components/OrganizationSettings.js @@ -0,0 +1,187 @@ +import React from 'react'; +import cn from 'classnames'; +import { Button } from 'coral-ui'; +import PropTypes from 'prop-types'; +import styles from './OrganizationSettings.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'; +import validate from 'coral-framework/helpers/validate'; +import errorMsj from 'coral-framework/helpers/error'; + +class OrganizationSettings extends React.Component { + state = { editing: false, errors: [] }; + + addError = err => { + if (this.state.errors.indexOf(err) === -1) { + this.setState(({ errors }) => ({ + errors: errors.concat(err), + })); + } + }; + + removeError = err => { + this.setState(({ errors }) => ({ + errors: errors.filter(i => i !== err), + })); + }; + + toggleEditing = () => { + this.setState(({ editing }) => ({ + editing: !editing, + })); + }; + + disableEditing = () => { + this.setState(() => ({ + editing: false, + })); + }; + + updateName = event => { + const updater = { organizationName: { $set: event.target.value } }; + this.props.updatePending({ updater }); + }; + + updateEmail = event => { + let error = null; + const email = event.target.value; + + // Add a blocker error + if (!validate.email(email)) { + error = true; + this.addError('email'); + } else { + this.removeError('email'); + } + + const updater = { organizationContactEmail: { $set: email } }; + const errorUpdater = { organizationEmail: { $set: error } }; + + this.props.updatePending({ updater, errorUpdater }); + }; + + cancelEditing = () => { + this.disableEditing(); + this.props.clearPending(); + }; + + save = async () => { + await this.props.savePending(); + this.disableEditing(); + }; + + displayErrors = (errors = []) => ( +
    + {errors.map((errKey, i) => ( +
  • + {errorMsj[errKey]} +
  • + ))} +
+ ); + + render() { + const { settings, slotPassthrough, canSave } = this.props; + const hasErrors = this.state.errors.length; + + return ( + +

{t('configure.organization_info_copy')}

+

{t('configure.organization_info_copy_2')}

+ +
+
+ {this.displayErrors(this.state.errors)} +
    +
  • + + +
  • +
  • + + +
  • +
+
+ {!this.state.editing ? ( +
+ +
+ ) : ( +
+ {canSave && !hasErrors ? ( + + ) : ( + + )} + + {t('cancel')} + +
+ )} +
+
+ +
+ ); + } +} + +OrganizationSettings.propTypes = { + savePending: PropTypes.func.isRequired, + clearPending: PropTypes.func.isRequired, + updatePending: PropTypes.func.isRequired, + errors: PropTypes.object.isRequired, + settings: PropTypes.object.isRequired, + slotPassthrough: PropTypes.object.isRequired, + canSave: PropTypes.bool.isRequired, +}; + +export default OrganizationSettings; diff --git a/client/coral-admin/src/routes/Configure/containers/Configure.js b/client/coral-admin/src/routes/Configure/containers/Configure.js index 9f980d31b..7f0951154 100644 --- a/client/coral-admin/src/routes/Configure/containers/Configure.js +++ b/client/coral-admin/src/routes/Configure/containers/Configure.js @@ -16,6 +16,7 @@ import { hideSaveDialog, } from '../../../actions/configure'; import Configure from '../components/Configure'; +import OrganizationSettings from './OrganizationSettings'; import { withRouter } from 'react-router'; class ConfigureContainer extends React.Component { @@ -83,18 +84,21 @@ class ConfigureContainer extends React.Component { return ; } + const activeSection = this.props.routes[3].path; + return ( {this.props.children} @@ -110,10 +114,12 @@ const withConfigureQuery = withQuery( ...${getDefinitionName(StreamSettings.fragments.settings)} ...${getDefinitionName(TechSettings.fragments.settings)} ...${getDefinitionName(ModerationSettings.fragments.settings)} + ...${getDefinitionName(OrganizationSettings.fragments.settings)} } ...${getDefinitionName(StreamSettings.fragments.root)} ...${getDefinitionName(TechSettings.fragments.root)} ...${getDefinitionName(ModerationSettings.fragments.root)} + ...${getDefinitionName(OrganizationSettings.fragments.root)} } ${StreamSettings.fragments.root} ${StreamSettings.fragments.settings} @@ -121,6 +127,8 @@ const withConfigureQuery = withQuery( ${TechSettings.fragments.settings} ${ModerationSettings.fragments.root} ${ModerationSettings.fragments.settings} + ${OrganizationSettings.fragments.root} + ${OrganizationSettings.fragments.settings} `, { options: () => ({ diff --git a/client/coral-admin/src/routes/Configure/containers/OrganizationSettings.js b/client/coral-admin/src/routes/Configure/containers/OrganizationSettings.js new file mode 100644 index 000000000..79ab70f69 --- /dev/null +++ b/client/coral-admin/src/routes/Configure/containers/OrganizationSettings.js @@ -0,0 +1,53 @@ +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import { compose, gql } from 'react-apollo'; +import OrganizationSettings from '../components/OrganizationSettings'; +import withFragments from 'coral-framework/hocs/withFragments'; +import { getSlotFragmentSpreads } from 'coral-framework/utils'; +import { updatePending } from '../../../actions/configure'; +import { mapProps } from 'recompose'; + +const slots = ['adminOrganizationSettings']; + +const mapStateToProps = state => ({ + errors: state.configure.errors, +}); + +const mapDispatchToProps = dispatch => + bindActionCreators( + { + updatePending, + }, + dispatch + ); + +export default compose( + withFragments({ + root: gql` + fragment TalkAdmin_OrganizationSettings_root on RootQuery { + __typename + ${getSlotFragmentSpreads(slots, 'root')} + } + `, + settings: gql` + fragment TalkAdmin_OrganizationSettings_settings on Settings { + organizationName + organizationContactEmail + ${getSlotFragmentSpreads(slots, 'settings')} + } + `, + }), + connect(mapStateToProps, mapDispatchToProps), + mapProps(({ root, settings, updatePending, errors, ...rest }) => ({ + slotPassthrough: { + root, + settings, + updatePending, + errors, + }, + updatePending, + settings, + errors, + ...rest, + })) +)(OrganizationSettings); diff --git a/client/coral-admin/src/routes/Install/components/Install.js b/client/coral-admin/src/routes/Install/components/Install.js index 14afc12f5..4e0024102 100644 --- a/client/coral-admin/src/routes/Install/components/Install.js +++ b/client/coral-admin/src/routes/Install/components/Install.js @@ -1,15 +1,16 @@ -import React, { Component } from 'react'; +import React from 'react'; import styles from './Install.css'; import { Wizard, WizardNav } from 'coral-ui'; import Layout from 'coral-admin/src/components/Layout'; +import PropTypes from 'prop-types'; import InitialStep from './Steps/InitialStep'; -import AddOrganizationName from './Steps/AddOrganizationName'; +import OrganizationDetails from './Steps/OrganizationDetails'; import CreateYourAccount from './Steps/CreateYourAccount'; import PermittedDomainsStep from './Steps/PermittedDomainsStep'; import FinalStep from './Steps/FinalStep'; -export default class Install extends Component { +class Install extends React.Component { handleDomainsChange = value => { this.props.updatePermittedDomains(value); }; @@ -55,7 +56,7 @@ export default class Install extends Component { goToStep={this.props.goToStep} > - { showErrors={install.showErrors} errorMsg={install.errors.organizationName} /> + + +