diff --git a/client/coral-admin/src/actions/configure.js b/client/coral-admin/src/actions/configure.js new file mode 100644 index 000000000..128de75bc --- /dev/null +++ b/client/coral-admin/src/actions/configure.js @@ -0,0 +1,13 @@ +import * as actions from 'constants/configure'; + +export const updatePending = ({updater, errorUpdater}) => { + return {type: actions.UPDATE_PENDING, updater, errorUpdater}; +}; + +export const clearPending = () => { + return {type: actions.CLEAR_PENDING}; +}; + +export const setActiveSection = (section) => { + return {type: actions.SET_ACTIVE_SECTION, section}; +}; diff --git a/client/coral-admin/src/actions/settings.js b/client/coral-admin/src/actions/settings.js deleted file mode 100644 index 35f2013c9..000000000 --- a/client/coral-admin/src/actions/settings.js +++ /dev/null @@ -1,58 +0,0 @@ -import t from 'coral-framework/services/i18n'; - -export const SETTINGS_LOADING = 'SETTINGS_LOADING'; -export const SETTINGS_RECEIVED = 'SETTINGS_RECEIVED'; -export const SETTINGS_FETCH_ERROR = 'SETTINGS_FETCH_ERROR'; - -export const SETTINGS_UPDATED = 'SETTINGS_UPDATED'; - -export const SAVE_SETTINGS_LOADING = 'SAVE_SETTINGS_LOADING'; -export const SAVE_SETTINGS_SUCCESS = 'SAVE_SETTINGS_SUCCESS'; -export const SAVE_SETTINGS_FAILED = 'SAVE_SETTINGS_FAILED'; - -export const WORDLIST_UPDATED = 'WORDLIST_UPDATED'; -export const DOMAINLIST_UPDATED = 'DOMAINLIST_UPDATED'; - -export const fetchSettings = () => (dispatch, _, {rest}) => { - dispatch({type: SETTINGS_LOADING}); - rest('/settings') - .then((settings) => { - dispatch({type: SETTINGS_RECEIVED, settings}); - }) - .catch((error) => { - console.error(error); - const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); - dispatch({type: SETTINGS_FETCH_ERROR, error: errorMessage}); - }); -}; - -// for updating top-level settings -export const updateSettings = (settings) => { - return {type: SETTINGS_UPDATED, settings}; -}; - -// this is a nested property, so it needs a special action. -export const updateWordlist = (listName, list) => { - return {type: WORDLIST_UPDATED, listName, list}; -}; - -export const updateDomainlist = (listName, list) => { - return {type: DOMAINLIST_UPDATED, listName, list}; -}; - -export const saveSettingsToServer = () => (dispatch, getState, {rest}) => { - let settings = getState().settings; - if (settings.charCount) { - settings.charCount = parseInt(settings.charCount); - } - dispatch({type: SAVE_SETTINGS_LOADING}); - rest('/settings', {method: 'PUT', body: settings}) - .then(() => { - dispatch({type: SAVE_SETTINGS_SUCCESS, settings}); - }) - .catch((error) => { - console.error(error); - const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); - dispatch({type: SAVE_SETTINGS_FAILED, error: errorMessage}); - }); -}; diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index a5955af45..275503085 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -17,8 +17,6 @@ export default class UserDetail extends React.Component { userId: PropTypes.string.isRequired, hideUserDetail: PropTypes.func.isRequired, root: PropTypes.object.isRequired, - bannedWords: PropTypes.array.isRequired, - suspectWords: PropTypes.array.isRequired, acceptComment: PropTypes.func.isRequired, rejectComment: PropTypes.func.isRequired, changeStatus: PropTypes.func.isRequired, @@ -79,8 +77,6 @@ export default class UserDetail extends React.Component { }, activeTab, selectedCommentIds, - bannedWords, - suspectWords, toggleSelect, bulkAccept, bulkReject, @@ -184,8 +180,6 @@ export default class UserDetail extends React.Component { root={root} data={data} comment={comment} - suspectWords={suspectWords} - bannedWords={bannedWords} acceptComment={this.acceptThenReload} rejectComment={this.rejectThenReload} selected={selected} diff --git a/client/coral-admin/src/components/UserDetailComment.js b/client/coral-admin/src/components/UserDetailComment.js index f8d81ce8b..e3e89d00a 100644 --- a/client/coral-admin/src/components/UserDetailComment.js +++ b/client/coral-admin/src/components/UserDetailComment.js @@ -30,12 +30,11 @@ class UserDetailComment extends React.Component { render() { const { comment, - suspectWords, - bannedWords, selected, toggleSelect, className, data, + root: {settings: {wordlist: {banned, suspect}}}, } = this.props; return ( @@ -72,8 +71,8 @@ class UserDetailComment extends React.Component {
{' '} @@ -123,9 +122,15 @@ UserDetailComment.propTypes = { acceptComment: PropTypes.func.isRequired, rejectComment: PropTypes.func.isRequired, className: PropTypes.string, - suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired, - bannedWords: PropTypes.arrayOf(PropTypes.string).isRequired, toggleSelect: PropTypes.func, + root: PropTypes.shape({ + settings: PropTypes.shape({ + wordlist: PropTypes.shape({ + suspect: PropTypes.arrayOf(PropTypes.string).isRequired, + banned: PropTypes.arrayOf(PropTypes.string).isRequired, + }), + }), + }), comment: PropTypes.shape({ id: PropTypes.string.isRequired, status: PropTypes.string.isRequired, @@ -136,8 +141,8 @@ UserDetailComment.propTypes = { title: PropTypes.string, url: PropTypes.string, id: PropTypes.string - }) - }) + }), + }), }; export default UserDetailComment; diff --git a/client/coral-admin/src/constants/configure.js b/client/coral-admin/src/constants/configure.js new file mode 100644 index 000000000..05673b5aa --- /dev/null +++ b/client/coral-admin/src/constants/configure.js @@ -0,0 +1,5 @@ +const prefix = 'TALK_ADMIN_CONFIGURE'; + +export const UPDATE_PENDING = `${prefix}_UPDATE_PENDING`; +export const CLEAR_PENDING = `${prefix}_CLEAR_PENDING`; +export const SET_ACTIVE_SECTION = `${prefix}_SET_ACTIVE_SECTION`; diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index 2ae842b1d..ffc661150 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -179,8 +179,6 @@ const mapStateToProps = (state) => ({ selectedCommentIds: state.userDetail.selectedCommentIds, statuses: state.userDetail.statuses, activeTab: state.userDetail.activeTab, - bannedWords: state.settings.wordlist.banned, - suspectWords: state.settings.wordlist.suspect, }); const mapDispatchToProps = (dispatch) => ({ diff --git a/client/coral-admin/src/containers/UserDetailComment.js b/client/coral-admin/src/containers/UserDetailComment.js index 9eef5934f..fe95f4ae4 100644 --- a/client/coral-admin/src/containers/UserDetailComment.js +++ b/client/coral-admin/src/containers/UserDetailComment.js @@ -8,7 +8,12 @@ import CommentDetails from './CommentDetails'; export default withFragments({ root: gql` fragment CoralAdmin_UserDetailComment_root on RootQuery { - __typename + settings { + wordlist { + banned + suspect + } + } ...${getDefinitionName(CommentLabels.fragments.root)} ...${getDefinitionName(CommentDetails.fragments.root)} } diff --git a/client/coral-admin/src/graphql/index.js b/client/coral-admin/src/graphql/index.js index 6b8aa7456..d7656d9a5 100644 --- a/client/coral-admin/src/graphql/index.js +++ b/client/coral-admin/src/graphql/index.js @@ -1,4 +1,15 @@ import update from 'immutability-helper'; +import mapValues from 'lodash/mapValues'; + +// Map nested object leaves. Array objects are considered leaves. +function mapLeaves(o, mapper) { + return mapValues(o, (val) => { + if (typeof val === 'object' && !Array.isArray(val)) { + return mapLeaves(val, mapper); + } + return mapper(val); + }); +} export default { mutations: { @@ -29,6 +40,16 @@ export default { } } }), + UpdateSettings: ({variables: {input}}) => ({ + updateQueries: { + TalkAdmin_Configure: (prev) => { + const updated = update(prev, { + settings: mapLeaves(input, (leaf) => ({$set: leaf})), + }); + return updated; + } + } + }), }, }; diff --git a/client/coral-admin/src/reducers/configure.js b/client/coral-admin/src/reducers/configure.js new file mode 100644 index 000000000..e03c9ea8f --- /dev/null +++ b/client/coral-admin/src/reducers/configure.js @@ -0,0 +1,47 @@ +import * as actions from '../constants/configure'; +import isEmpty from 'lodash/isEmpty'; +import update from 'immutability-helper'; + +const initialState = { + canSave: false, + pending: {}, + errors: {}, + activeSection: 'stream', +}; + +export default function configure(state = initialState, action) { + switch (action.type) { + case actions.UPDATE_PENDING: { + let next = state; + if (action.updater) { + next = update(next, { + pending: action.updater, + }); + } + if (action.errorUpdater) { + next = update(next, { + errors: action.errorUpdater, + }); + } + const noErrors = Object.keys(next.errors).reduce((res, error) => res && !next.errors[error], true); + const canSave = !isEmpty(next.pending) && noErrors; + next = update(next, { + canSave: {$set: canSave}, + }); + + return next; + } + case actions.CLEAR_PENDING: + return { + ...state, + pending: {}, + canSave: false, + }; + case actions.SET_ACTIVE_SECTION: + return { + ...state, + activeSection: action.section, + }; + } + return state; +} diff --git a/client/coral-admin/src/reducers/dashboard.js b/client/coral-admin/src/reducers/dashboard.js new file mode 100644 index 000000000..3b3414d3a --- /dev/null +++ b/client/coral-admin/src/reducers/dashboard.js @@ -0,0 +1,15 @@ +// this is initialized here because +// currently you have to reload the dashboard to get new stats +// cleaner updates are planned in the future. +const DASHBOARD_WINDOW_MINUTES = 5; +let then = new Date(); +then.setMinutes(then.getMinutes() - DASHBOARD_WINDOW_MINUTES); + +const initialState = { + windowStart: then.toISOString(), + windowEnd: new Date().toISOString(), +}; + +export default function dashboard (state = initialState, _action) { + return state; +} diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index b329675a4..372fedbae 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -1,6 +1,7 @@ import auth from './auth'; import assets from './assets'; -import settings from './settings'; +import dashboard from './dashboard'; +import configure from './configure'; import community from './community'; import moderation from './moderation'; import install from './install'; @@ -12,10 +13,11 @@ import userDetail from './userDetail'; export default { auth, banUserDialog, + dashboard, + configure, suspendUserDialog, userDetail, assets, - settings, community, moderation, install, diff --git a/client/coral-admin/src/reducers/settings.js b/client/coral-admin/src/reducers/settings.js deleted file mode 100644 index 336047e5b..000000000 --- a/client/coral-admin/src/reducers/settings.js +++ /dev/null @@ -1,92 +0,0 @@ -import * as actions from '../actions/settings'; -import update from 'immutability-helper'; - -// this is initialized here because -// currently you have to reload the dashboard to get new stats -// cleaner updates are planned in the future. -// TODO: if there are more than two fields for the dashboard being created here, -// please create a new reducer specifically for the Dashboard. -const DASHBOARD_WINDOW_MINUTES = 5; -let then = new Date(); -then.setMinutes(then.getMinutes() - DASHBOARD_WINDOW_MINUTES); - -const initialState = { - wordlist: { - banned: [], - suspect: [] - }, - dashboardWindowStart: then.toISOString(), - dashboardWindowEnd: new Date().toISOString(), - domains: { - whitelist: [] - }, - saveSettingsError: null, - fetchSettingsError: null, - fetchingSettings: false -}; - -export default function settings (state = initialState, action) { - switch (action.type) { - case actions.SETTINGS_LOADING: - return { - ...state, - fetchingSettings: true, - fetchSettingsError: null, - }; - case actions.SETTINGS_RECEIVED: - return { - ...state, - fetchingSettings: false, - fetchSettingsError: null, - ...action.settings - }; - case actions.SETTINGS_FETCH_ERROR: - return { - ...state, - fetchingSettings: false, - fetchSettingsError: action.error, - }; - case actions.SETTINGS_UPDATED: - return { - ...state, - fetchingSettings: false, - fetchSettingsError: null, - ...action.settings - }; - case actions.SAVE_SETTINGS_LOADING: - return { - ...state, - fetchingSettings: true, - saveSettingsError: null, - }; - case actions.SAVE_SETTINGS_SUCCESS: - return { - ...state, - fetchingSettings: false, - fetchSettingsError: null, - ...action.settings - }; - case actions.SAVE_SETTINGS_FAILED: - return { - ...state, - fetchingSettings: false, - fetchSettingsError: action.error, - }; - case actions.WORDLIST_UPDATED: - return update(state, { - wordlist: { - [action.listName]: { - $set: action.list - } - } - }); - case actions.DOMAINLIST_UPDATED: - return update(state, { - domains: { - [action.listName]: {$set: action.list}, - } - }); - default: - return state; - } -} diff --git a/client/coral-admin/src/routes/Configure/components/Configure.css b/client/coral-admin/src/routes/Configure/components/Configure.css index 5faf41ba2..33a0c9c6f 100644 --- a/client/coral-admin/src/routes/Configure/components/Configure.css +++ b/client/coral-admin/src/routes/Configure/components/Configure.css @@ -1,19 +1,7 @@ -/** - * @TODO: deprecated as this file contains styles from multiple components. Please refactor. - */ - .container { max-width: 1280px; margin: 0 auto; display: flex; - max-width: 1280px; - margin: 0 auto; - - h3 { - color: black; - font-size: 1.26em; - font-weight: 500; - } } .leftColumn { @@ -21,6 +9,15 @@ width: 234px; } +.saveBox { + margin-top: 38px; +} + +.changedSave { + background-color: #00796B; + color: white; +} + .mainContent { width: calc(100% - 300px); padding: 10px 14px; @@ -28,183 +25,3 @@ max-width: 718px; } -.configSetting { - margin-bottom: 20px; - align-items: flex-start; - min-height: 100px; - max-width: 600px; - - h3 { - margin: 0; - } - - .actions { - display: inline-block; - width: 100%; - - .copiedText { - display: inline-block; - color: #00796b; - padding: 12px; - font-size: 14px; - float: right; - } - - .copyButton { - display: inline-block; - width: 200px; - float: right; - } - } -} - -.settingsError { - color: #d50000; -} - -.settingsError i { - font-size: 14px; - margin-right: 3px; -} - -.settingsHeader { - margin-top: 3px; - margin-bottom: 7px; - font-size: 18px; - font-weight: 500; -} - -.disabledSettingText { - color: #ccc; -} - -.configSettingInfoBox { - min-height: 100px; - margin-bottom: 20px; - width: auto; - height: auto; - text-align: left; - overflow: visible; -} - -.configSettingEmbed { - border: 1px solid #ccc; - border-radius: 4px; - margin-bottom: 10px; - display: block; -} - -.configTimeoutSelect { - display: inline-block; - margin-left: 20px; - - i { /* fix for firefox and react-mdl-selectfield@0.2.0 */ - padding: 20px 0; - vertical-align: top; - } -} - -.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; -} - -.changedSave { - background-color: #00796B; - color: white; -} - -.embedInput { - width: 100%; - display: block; - outline: none; - border: 1px solid rgba(0,0,0,.12); - padding: 6px; - box-sizing: border-box; - border-radius: 2px; - margin: 5px auto; - min-height: 175px; - font-size: 14px; - resize: none; -} - -.customCSSInput { - width: 100%; - font-size: 14px; - padding: 14px; - letter-spacing: 0.03em; - color: #555; - box-sizing: border-box; -} - -.enabledSetting { - border-left-color: #00796b; - border-left-style: solid; - border-left-width: 7px; -} - -.disabledSetting { - padding-left: 22px; -} - -.hidden { - display: none; -} - -.saveBox { - margin-top: 38px; -} - -.settingsSection { - padding-bottom: 200px; - .action { - display: inline-block; - position: absolute; - top: 0; - left: 0; - padding: 20px; - } - - .content { - display: inline-block; - padding: 0px 30px; - box-sizing: border-box; - width: 100%; - } -} - -.Configure { - - p { - line-height: 1.2; - max-width: 550px; - } - - .wrapper { - width: 100%; - } - - .descriptionBox { - margin-top: 15px; - - input { - height: 150px; - } - } -} diff --git a/client/coral-admin/src/routes/Configure/components/Configure.js b/client/coral-admin/src/routes/Configure/components/Configure.js index 15ff69b69..ecdbddcc0 100644 --- a/client/coral-admin/src/routes/Configure/components/Configure.js +++ b/client/coral-admin/src/routes/Configure/components/Configure.js @@ -1,119 +1,40 @@ import React, {Component} from 'react'; -import {Button, List, Item, Card, Spinner} from 'coral-ui'; +import {Button, List, Item} from 'coral-ui'; import styles from './Configure.css'; -import StreamSettings from './StreamSettings'; -import ModerationSettings from './ModerationSettings'; -import TechSettings from './TechSettings'; +import StreamSettings from '../containers/StreamSettings'; +import ModerationSettings from '../containers/ModerationSettings'; +import TechSettings from '../containers/TechSettings'; import t from 'coral-framework/services/i18n'; import {can} from 'coral-framework/services/perms'; +import PropTypes from 'prop-types'; export default class Configure extends Component { - state = { - activeSection: 'stream', - changed: false, - errors: {} - }; - - saveSettings = () => { - this.props.saveSettingsToServer(); - this.setState({changed: false}); - } - - changeSection = (activeSection) => { - this.setState({activeSection}); - } - - onChangeWordlist = (listName, list) => { - this.setState({changed: true}); - this.props.updateWordlist(listName, list); - } - - onChangeDomainlist = (listName, list) => { - this.setState({changed: true}); - this.props.updateDomainlist(listName, list); - } - - onSettingUpdate = (setting) => { - this.setState({changed: true}); - this.props.updateSettings(setting); - } - - // Sets an arbitrary error string and a boolean state. - // This allows the system to track multiple errors. - onSettingError = (error, state) => { - this.setState((prevState) => { - prevState.errors[error] = state; - return prevState; - }); - } - - getSection (section) { - const pageTitle = this.getPageTitle(section); - let sectionComponent; + getSectionComponent(section) { switch(section){ case 'stream': - sectionComponent = ; - break; + return StreamSettings; case 'moderation': - sectionComponent = ; - break; + return ModerationSettings; case 'tech': - sectionComponent = ; - } - - if (this.props.settings.fetchingSettings) { - return Loading settings...; - } - - return ( -
-

{pageTitle}

- {sectionComponent} -
- ); - } - - getPageTitle (section) { - switch(section) { - case 'stream': - return t('configure.stream_settings'); - case 'moderation': - return t('configure.moderation_settings'); - case 'tech': - return t('configure.tech_settings'); - default: - return ''; + return TechSettings; } + throw new Error(`Unknown section ${section}`); } render () { - const {activeSection} = this.state; - const section = this.getSection(activeSection); - const {auth: {user}} = this.props; + const {auth: {user}, canSave, savePending, setActiveSection, activeSection} = this.props; + const SectionComponent = this.getSectionComponent(activeSection); if (!can(user, 'UPDATE_CONFIG')) { return

You must be an administrator to access config settings. Please find the nearest Admin and ask them to level you up!

; } - const showSave = Object.keys(this.state.errors).reduce( - (bool, error) => this.state.errors[error] ? false : bool, this.state.changed); - return (
- + {t('configure.stream_settings')} @@ -126,10 +47,10 @@ export default class Configure extends Component {
{ - showSave ? + canSave ?
); } } + +Configure.propTypes = { + notify: PropTypes.func.isRequired, + savePending: PropTypes.func.isRequired, + auth: PropTypes.object.isRequired, + data: PropTypes.object.isRequired, + root: PropTypes.object.isRequired, + settings: PropTypes.object.isRequired, + canSave: PropTypes.bool.isRequired, + setActiveSection: PropTypes.func.isRequired, + activeSection: PropTypes.string.isRequired, +}; diff --git a/client/coral-admin/src/routes/Configure/components/ConfigurePage.css b/client/coral-admin/src/routes/Configure/components/ConfigurePage.css new file mode 100644 index 000000000..f592f0383 --- /dev/null +++ b/client/coral-admin/src/routes/Configure/components/ConfigurePage.css @@ -0,0 +1,5 @@ +.title { + color: black; + font-size: 1.26em; + font-weight: 500; +} diff --git a/client/coral-admin/src/routes/Configure/components/ConfigurePage.js b/client/coral-admin/src/routes/Configure/components/ConfigurePage.js new file mode 100644 index 000000000..dc1182fc2 --- /dev/null +++ b/client/coral-admin/src/routes/Configure/components/ConfigurePage.js @@ -0,0 +1,17 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './ConfigurePage.css'; + +const ConfigurePage = ({title, children, ...rest}) => ( +
+

{title}

+ {children} +
+); + +ConfigurePage.propTypes = { + title: PropTypes.string.isRequired, + children: PropTypes.node, +}; + +export default ConfigurePage; diff --git a/client/coral-admin/src/routes/Configure/components/Domainlist.js b/client/coral-admin/src/routes/Configure/components/Domainlist.js index 7b7565c5e..9d2d624ac 100644 --- a/client/coral-admin/src/routes/Configure/components/Domainlist.js +++ b/client/coral-admin/src/routes/Configure/components/Domainlist.js @@ -1,25 +1,25 @@ import React from 'react'; -import {Card} from 'coral-ui'; -import styles from './Configure.css'; +import PropTypes from 'prop-types'; import TagsInput from 'coral-admin/src/components/TagsInput'; import t from 'coral-framework/services/i18n'; +import ConfigureCard from 'coral-framework/components/ConfigureCard'; const Domainlist = ({domains, onChangeDomainlist}) => { return ( - -
-
{t('configure.domain_list_title')}
-

{t('configure.domain_list_text')}

-
- onChangeDomainlist('whitelist', tags)} - /> -
-
-
+ +

{t('configure.domain_list_text')}

+ onChangeDomainlist('whitelist', tags)} + /> +
); }; +Domainlist.propTypes = { + domains: PropTypes.array.isRequired, + onChangeDomainlist: PropTypes.func.isRequired, +}; + export default Domainlist; diff --git a/client/coral-admin/src/routes/Configure/components/EmbedLink.css b/client/coral-admin/src/routes/Configure/components/EmbedLink.css new file mode 100644 index 000000000..07987df02 --- /dev/null +++ b/client/coral-admin/src/routes/Configure/components/EmbedLink.css @@ -0,0 +1,32 @@ +.embedInput { + width: 100%; + display: block; + outline: none; + border: 1px solid rgba(0,0,0,.12); + padding: 6px; + box-sizing: border-box; + border-radius: 2px; + margin: 5px auto; + min-height: 175px; + font-size: 14px; + resize: none; +} + +.copiedText { + display: inline-block; + color: #00796b; + padding: 12px; + font-size: 14px; + float: right; +} + +.copyButton { + display: inline-block; + width: 200px; + float: right; +} + +.actions { + display: inline-block; + width: 100%; +} diff --git a/client/coral-admin/src/routes/Configure/components/EmbedLink.js b/client/coral-admin/src/routes/Configure/components/EmbedLink.js index 1ce3d5304..aa1cddadc 100644 --- a/client/coral-admin/src/routes/Configure/components/EmbedLink.js +++ b/client/coral-admin/src/routes/Configure/components/EmbedLink.js @@ -1,17 +1,14 @@ import React, {Component} from 'react'; import t from 'coral-framework/services/i18n'; import join from 'url-join'; -import styles from './Configure.css'; -import {Button, Card} from 'coral-ui'; +import styles from './EmbedLink.css'; +import {Button} from 'coral-ui'; import {BASE_URL} from 'coral-framework/constants/url'; +import ConfigureCard from 'coral-framework/components/ConfigureCard'; class EmbedLink extends Component { - constructor (props) { - super(props); - - this.state = {copied: false}; - } + state = {copied: false}; copyToClipBoard = () => { const copyTextarea = document.querySelector(`.${styles.embedInput}`); @@ -38,21 +35,18 @@ class EmbedLink extends Component { "> `.trim(); return ( - -
-
Embed Comment Stream
-

{t('configure.copy_and_paste')}

-