Refactor admin

This commit is contained in:
Chi Vinh Le
2017-05-24 17:09:21 +07:00
parent f32208b35b
commit 8ab7047cfa
91 changed files with 620 additions and 689 deletions
+24 -29
View File
@@ -1,25 +1,20 @@
import React from 'react';
import {Router, Route, IndexRedirect, browserHistory} from 'react-router';
import Stories from 'containers/Stories/Stories';
import Configure from 'containers/Configure/Configure';
import LayoutContainer from 'containers/LayoutContainer';
import InstallContainer from 'containers/Install/InstallContainer';
import Configure from 'routes/Configure';
import Dashboard from 'routes/Dashboard';
import Install from 'routes/Install';
import Stories from 'routes/Stories';
import {CommunityLayout, Community} from 'routes/Community';
import {ModerationLayout, Moderation} from 'routes/Moderation';
import CommunityLayout from 'containers/Community/CommunityLayout';
import CommunityContainer from 'containers/Community/CommunityContainer';
import ModerationLayout from 'containers/ModerationQueue/ModerationLayout';
import ModerationContainer from 'containers/ModerationQueue/ModerationContainer';
import Dashboard from 'containers/Dashboard/Dashboard';
import Layout from 'containers/Layout';
const routes = (
<div>
<Route exact path="/admin/install" component={InstallContainer}/>
<Route path='/admin' component={LayoutContainer}>
<Route exact path="/admin/install" component={Install}/>
<Route path='/admin' component={Layout}>
<IndexRedirect to='/admin/moderate/all' />
<Route path='community' component={CommunityContainer} />
<Route path='configure' component={Configure} />
<Route path='stories' component={Stories} />
<Route path='dashboard' component={Dashboard} />
@@ -27,11 +22,11 @@ const routes = (
{/* Community Routes */}
<Route path='community' component={CommunityLayout}>
<Route path='flagged' components={CommunityContainer}>
<Route path=':id' components={CommunityContainer} />
<Route path='flagged' components={Community}>
<Route path=':id' components={Community} />
</Route>
<Route path='people' components={CommunityContainer}>
<Route path=':id' components={CommunityContainer} />
<Route path='people' components={Community}>
<Route path=':id' components={Community} />
</Route>
<IndexRedirect to='flagged' />
</Route>
@@ -39,22 +34,22 @@ const routes = (
{/* Moderation Routes */}
<Route path='moderate' component={ModerationLayout}>
<Route path='all' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
<Route path='all' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Route path='accepted' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
<Route path='accepted' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Route path='premod' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
<Route path='premod' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Route path='rejected' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
<Route path='rejected' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Route path='flagged' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
<Route path='flagged' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Route path=':id' components={ModerationContainer} />
<Route path=':id' components={Moderation} />
<IndexRedirect to='premod' />
</Route>
</Route>
+1 -1
View File
@@ -5,7 +5,7 @@ export const singleView = () => ({type: actions.SINGLE_VIEW});
// Ban User Dialog
export const showBanUserDialog = (user, commentId, commentStatus, showRejectedNote) => ({type: actions.SHOW_BANUSER_DIALOG, user, commentId, commentStatus, showRejectedNote});
export const hideBanUserDialog = (showDialog) => ({type: actions.HIDE_BANUSER_DIALOG, showDialog});
export const hideBanUserDialog = () => ({type: actions.HIDE_BANUSER_DIALOG});
// Suspend User Dialog
export const showSuspendUserDialog = (userId, username, commentId, commentStatus) =>
@@ -1,7 +1,7 @@
import React, {PropTypes} from 'react';
import styles from './ModerationList.css';
import {Button} from 'coral-ui';
import {menuActionsMap} from '../containers/ModerationQueue/helpers/moderationQueueActionsMap';
import {menuActionsMap} from '../routes/Moderation/helpers/moderationQueueActionsMap';
const ActionButton = ({type = '', active, ...props}) => {
const typeName = type.toLowerCase();
@@ -1,81 +0,0 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {SelectField, Option} from 'react-mdl-selectfield';
import styles from './Community.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations';
import {setRole, setCommenterStatus} from '../../actions/community';
const lang = new I18n(translations);
class Table extends Component {
constructor (props) {
super(props);
this.onRoleChange = this.onRoleChange.bind(this);
}
onRoleChange (id, role) {
this.props.dispatch(setRole(id, role));
}
onCommenterStatusChange (id, status) {
this.props.dispatch(setCommenterStatus(id, status));
}
render () {
const {headers, commenters, onHeaderClickHandler} = this.props;
return (
<table className={`mdl-data-table ${styles.dataTable}`}>
<thead>
<tr>
{headers.map((header, i) =>(
<th
key={i}
className="mdl-data-table__cell--non-numeric"
onClick={() => onHeaderClickHandler({field: header.field})}>
{header.title}
</th>
))}
</tr>
</thead>
<tbody>
{commenters.map((row, i)=> (
<tr key={i}>
<td className="mdl-data-table__cell--non-numeric">
{row.username}
<span className={styles.email}>{row.profiles.map(({id}) => id)}</span>
</td>
<td className="mdl-data-table__cell--non-numeric">
{row.created_at}
</td>
<td className="mdl-data-table__cell--non-numeric">
<SelectField label={'Select me'} value={row.status || ''}
className={styles.selectField}
label={lang.t('community.status')}
onChange={(status) => this.onCommenterStatusChange(row.id, status)}>
<Option value={'ACTIVE'}>{lang.t('community.active')}</Option>
<Option value={'BANNED'}>{lang.t('community.banned')}</Option>
</SelectField>
</td>
<td className="mdl-data-table__cell--non-numeric">
<SelectField label={'Select me'} value={row.roles[0] || ''}
className={styles.selectField}
label={lang.t('community.role')}
onChange={(role) => this.onRoleChange(row.id, role)}>
<Option value={''}>.</Option>
<Option value={'STAFF'}>{lang.t('community.staff')}</Option>
<Option value={'MODERATOR'}>{lang.t('community.moderator')}</Option>
<Option value={'ADMIN'}>{lang.t('community.admin')}</Option>
</SelectField>
</td>
</tr>
))}
</tbody>
</table>
);
}
}
export default connect((state) => ({commenters: state.community.get('accounts')}))(Table);
@@ -1,48 +0,0 @@
import React from 'react';
import styles from './Dashboard.css';
import {compose} from 'react-apollo';
import {connect} from 'react-redux';
import {getMetrics} from 'coral-admin/src/graphql/queries';
import FlagWidget from './FlagWidget';
import ActivityWidget from './ActivityWidget';
import CountdownTimer from 'coral-admin/src/components/CountdownTimer';
import {Spinner} from 'coral-ui';
class Dashboard extends React.Component {
reloadData = () => {
this.props.data.refetch();
}
render () {
if (this.props.data && this.props.data.loading) {
return <Spinner />;
}
const {data: {assetsByActivity, assetsByFlag}} = this.props;
return (
<div>
<CountdownTimer handleTimeout={this.reloadData} />
<div className={styles.Dashboard}>
<FlagWidget assets={assetsByFlag} />
<ActivityWidget assets={assetsByActivity} />
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
settings: state.settings.toJS(),
moderation: state.moderation.toJS()
};
};
export default compose(
connect(mapStateToProps),
getMetrics
)(Dashboard);
@@ -1,101 +0,0 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import styles from './style.css';
import {Wizard, WizardNav} from 'coral-ui';
import Layout from 'coral-admin/src/components/ui/Layout';
import {
goToStep,
nextStep,
submitUser,
checkInstall,
previousStep,
finishInstall,
submitSettings,
updateUserFormData,
updateSettingsFormData,
updatePermittedDomains
} from '../../actions/install';
import InitialStep from './components/Steps/InitialStep';
import AddOrganizationName from './components/Steps/AddOrganizationName';
import CreateYourAccount from './components/Steps/CreateYourAccount';
import PermittedDomainsStep from './components/Steps/PermittedDomainsStep';
import FinalStep from './components/Steps/FinalStep';
class InstallContainer extends Component {
componentDidMount() {
const {checkInstall} = this.props;
checkInstall(() => {
this.context.router.push('/admin');
});
}
render() {
const {install} = this.props;
return (
<Layout restricted={true}>
<div className={styles.Install}>
{
!install.alreadyInstalled ? (
<div>
<h2>Welcome to the Coral Project</h2>
{ install.step !== 0 ? <WizardNav items={install.navItems} currentStep={install.step} icon='check'/> : null }
<Wizard currentStep={install.step} {...this.props}>
<InitialStep/>
<AddOrganizationName/>
<CreateYourAccount/>
<PermittedDomainsStep/>
<FinalStep/>
</Wizard>
</div>
) : (
<div>Talk is already installed</div>
)
}
</div>
</Layout>
);
}
}
InstallContainer.contextTypes = {
router: React.PropTypes.object
};
const mapStateToProps = (state) => ({
install: state.install.toJS()
});
const mapDispatchToProps = (dispatch) => ({
nextStep: () => dispatch(nextStep()),
goToStep: (step) => dispatch(goToStep(step)),
previousStep: () => dispatch(previousStep()),
finishInstall: () => dispatch(finishInstall()),
checkInstall: (next) => dispatch(checkInstall(next)),
handleDomainsChange: (value) => {
dispatch(updatePermittedDomains(value));
},
handleSettingsChange: (e) => {
const {name, value} = e.currentTarget;
dispatch(updateSettingsFormData(name, value));
},
handleUserChange: (e) => {
const {name, value} = e.currentTarget;
dispatch(updateUserFormData(name, value));
},
handleSettingsSubmit: (e) => {
e.preventDefault();
dispatch(submitSettings());
},
handleUserSubmit: (e) => {
e.preventDefault();
dispatch(submitUser());
}
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(InstallContainer);
@@ -1,187 +0,0 @@
import React, {Component} from 'react';
import styles from './Stories.css';
import {connect} from 'react-redux';
import I18n from 'coral-framework/modules/i18n/i18n';
import {fetchAssets, updateAssetState} from '../../actions/assets';
import translations from '../../translations.json';
import {Link} from 'react-router';
import {Pager, Icon} from 'coral-ui';
import {DataTable, TableHeader, RadioGroup, Radio} from 'react-mdl';
import EmptyCard from 'coral-admin/src/components/EmptyCard';
const limit = 25;
class Stories extends Component {
state = {
search: '',
sort: 'desc',
filter: 'all',
statusMenus: {},
timer: null,
page: 0
}
componentDidMount () {
this.props.fetchAssets(0, limit, '', this.state.sortBy);
}
onSettingChange = (setting) => (e) => {
let options = this.state;
this.setState({[setting]: e.target.value});
options[setting] = e.target.value;
this.props.fetchAssets(0, limit, options.search, options.sort, options.filter);
}
onSearchChange = (e) => {
const search = e.target.value;
this.setState((prevState) => {
prevState.search = search;
clearTimeout(prevState.timer);
const fetchAssets = this.props.fetchAssets;
prevState.timer = setTimeout(() => {
fetchAssets(0, limit, search, this.state.sort, this.state.filter);
}, 350);
return prevState;
});
}
renderDate = (date) => {
const d = new Date(date);
return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`;
}
onStatusClick = (closeStream, id, statusMenuOpen) => () => {
if (statusMenuOpen) {
this.setState((prev) => {
prev.statusMenus[id] = false;
return prev;
});
this.props.updateAssetState(id, closeStream ? Date.now() : null)
.then(() => {
const {search, sort, filter, page} = this.state;
this.props.fetchAssets(page, limit, search, sort, filter);
});
} else {
this.setState((prev) => {
prev.statusMenus[id] = true;
return prev;
});
}
}
renderTitle = (title, {id}) => <Link to={`/admin/moderate/${id}`}>{title}</Link>
renderStatus = (closedAt, {id}) => {
const closed = closedAt && new Date(closedAt).getTime() < Date.now();
const statusMenuOpen = this.state.statusMenus[id];
return <div className={styles.statusMenu}>
<div
className={closed ? styles.statusMenuClosed : styles.statusMenuOpen}
onClick={this.onStatusClick(closed, id, statusMenuOpen)}>
{!statusMenuOpen && <Icon className={styles.statusMenuIcon} name='keyboard_arrow_down'/>}
{closed ? lang.t('streams.closed') : lang.t('streams.open')}
</div>
{
statusMenuOpen &&
<div
className={!closed ? styles.statusMenuClosed : styles.statusMenuOpen}
onClick={this.onStatusClick(!closed, id, statusMenuOpen)}>
{!closed ? lang.t('streams.closed') : lang.t('streams.open')}
</div>
}
</div>;
}
onPageClick = (page) => {
this.setState({page});
const {search, sort, filter} = this.state;
this.props.fetchAssets((page - 1) * limit, limit, search, sort, filter);
}
render () {
const {search, sort, filter} = this.state;
const {assets} = this.props;
const assetsIds = assets.ids.map((id) => assets.byId[id]);
return (
<div className={styles.container}>
<div className={styles.leftColumn}>
<div className={styles.searchBox}>
<Icon name='search' className={styles.searchIcon}/>
<input
type='text'
value={search}
className={styles.searchBoxInput}
onChange={this.onSearchChange}
placeholder={lang.t('streams.search')}/>
</div>
<div className={styles.optionHeader}>{lang.t('streams.filter-streams')}</div>
<div className={styles.optionDetail}>{lang.t('streams.stream-status')}</div>
<RadioGroup
name='status filter'
value={filter}
childContainer='div'
onChange={this.onSettingChange('filter')}
className={styles.radioGroup}
>
<Radio value='all'>{lang.t('streams.all')}</Radio>
<Radio value='open'>{lang.t('streams.open')}</Radio>
<Radio value='closed'>{lang.t('streams.closed')}</Radio>
</RadioGroup>
<div className={styles.optionHeader}>{lang.t('streams.sort-by')}</div>
<RadioGroup
name='sort by'
value={sort}
childContainer='div'
onChange={this.onSettingChange('sort')}
className={styles.radioGroup}
>
<Radio value='desc'>{lang.t('streams.newest')}</Radio>
<Radio value='asc'>{lang.t('streams.oldest')}</Radio>
</RadioGroup>
</div>
{
assetsIds.length
? <div className={styles.mainContent}>
<DataTable className={styles.streamsTable} rows={assetsIds} onClick={this.goToModeration}>
<TableHeader name="title" cellFormatter={this.renderTitle}>{lang.t('streams.article')}</TableHeader>
<TableHeader name="publication_date" cellFormatter={this.renderDate}>
{lang.t('streams.pubdate')}
</TableHeader>
<TableHeader name="closedAt" cellFormatter={this.renderStatus} className={styles.status}>
{lang.t('streams.status')}
</TableHeader>
</DataTable>
<Pager
totalPages={Math.ceil((assets.count || 0) / limit)}
page={this.state.page}
onNewPageHandler={this.onPageClick} />
</div>
: <EmptyCard>{lang.t('streams.empty_result')}</EmptyCard>
}
</div>
);
}
}
const mapStateToProps = ({assets}) => {
return {
assets: assets.toJS()
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchAssets: (...args) => {
dispatch(fetchAssets.apply(this, args));
},
updateAssetState: (...args) => dispatch(updateAssetState.apply(this, args))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Stories);
const lang = new I18n(translations);
@@ -1,8 +1,8 @@
import React from 'react';
import styles from '../Community.css';
import styles from './Community.css';
import BanUserButton from './BanUserButton';
import {Button} from 'coral-ui';
import {menuActionsMap} from '../../../containers/ModerationQueue/helpers/moderationQueueActionsMap';
import {menuActionsMap} from '../../Moderation/helpers/moderationQueueActionsMap';
const ActionButton = ({type = '', user, ...props}) => {
if (type === 'BAN') {
@@ -1,55 +1,26 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {compose} from 'react-apollo';
import {modUserFlaggedQuery} from 'coral-admin/src/graphql/queries';
import {banUser, setUserStatus, rejectUsername} from 'coral-admin/src/graphql/mutations';
import {
fetchAccounts,
updateSorting,
newPage,
showBanUserDialog,
hideBanUserDialog,
showSuspendUserDialog,
hideSuspendUserDialog
} from '../../actions/community';
import CommunityMenu from './components/CommunityMenu';
import BanUserDialog from './components/BanUserDialog';
import SuspendUserDialog from './components/SuspendUserDialog';
import CommunityMenu from './CommunityMenu';
import BanUserDialog from './BanUserDialog';
import SuspendUserDialog from './SuspendUserDialog';
import People from './People';
import FlaggedAccounts from './FlaggedAccounts';
class CommunityContainer extends Component {
export default class Community extends Component {
constructor(props) {
super(props);
state = {
searchValue: '',
timer: null
};
this.state = {
searchValue: '',
timer: null
};
this.onKeyDownHandler = this.onKeyDownHandler.bind(this);
this.onSearchChange = this.onSearchChange.bind(this);
this.onHeaderClickHandler = this.onHeaderClickHandler.bind(this);
this.onNewPageHandler = this.onNewPageHandler.bind(this);
}
componentWillMount() {
this.props.fetchAccounts({});
}
onKeyDownHandler(e) {
onKeyDownHandler = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
this.search();
}
}
onSearchChange(e) {
onSearchChange = (e) => {
const value = e.target.value;
this.setState((prevState) => {
prevState.searchValue = value;
@@ -62,6 +33,16 @@ class CommunityContainer extends Component {
});
}
onHeaderClickHandler = (sort) => {
this.props.updateSorting(sort);
this.search();
}
onNewPageHandler = (page) => {
this.props.newPage(page);
this.search({page});
}
search(query = {}) {
const {community} = this.props;
@@ -71,17 +52,6 @@ class CommunityContainer extends Component {
asc: community.ascPeople,
...query
});
}
onHeaderClickHandler(sort) {
this.props.dispatch(updateSorting(sort));
this.search();
}
onNewPageHandler(page) {
this.props.dispatch(newPage(page));
this.search({page});
}
getTabContent(searchValue, props) {
@@ -134,7 +104,6 @@ class CommunityContainer extends Component {
render() {
const {searchValue} = this.state;
const tab = this.getTabContent(searchValue, this.props);
return (
@@ -148,22 +117,3 @@ class CommunityContainer extends Component {
}
}
const mapStateToProps = (state) => ({
community: state.community.toJS()
});
const mapDispatchToProps = (dispatch) => ({
fetchAccounts: (query) => dispatch(fetchAccounts(query)),
showBanUserDialog: (user) => dispatch(showBanUserDialog(user)),
hideBanUserDialog: () => dispatch(hideBanUserDialog(false)),
showSuspendUserDialog: (user) => dispatch(showSuspendUserDialog(user)),
hideSuspendUserDialog: () => dispatch(hideSuspendUserDialog())
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
modUserFlaggedQuery,
banUser,
setUserStatus,
rejectUsername
)(CommunityContainer);
@@ -8,7 +8,7 @@ import styles from './Community.css';
import Loading from './Loading';
import EmptyCard from 'coral-admin/src/components/EmptyCard';
import User from './components/User';
import User from './User';
const FlaggedAccounts = ({...props}) => {
const {commenters, isFetching} = props;
@@ -3,9 +3,9 @@ import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
import styles from './Community.css';
import Table from './Table';
import Table from '../containers/Table';
import {Pager, Icon} from 'coral-ui';
import EmptyCard from '../../components/EmptyCard';
import EmptyCard from '../../../components/EmptyCard';
const lang = new I18n(translations);
@@ -0,0 +1,57 @@
import React from 'react';
import {SelectField, Option} from 'react-mdl-selectfield';
import styles from '../components/Community.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../../translations';
const lang = new I18n(translations);
export default ({headers, commenters, onHeaderClickHandler, onRoleChange, onCommenterStatusChange}) => (
<table className={`mdl-data-table ${styles.dataTable}`}>
<thead>
<tr>
{headers.map((header, i) =>(
<th
key={i}
className="mdl-data-table__cell--non-numeric"
onClick={() => onHeaderClickHandler({field: header.field})}>
{header.title}
</th>
))}
</tr>
</thead>
<tbody>
{commenters.map((row, i)=> (
<tr key={i}>
<td className="mdl-data-table__cell--non-numeric">
{row.username}
<span className={styles.email}>{row.profiles.map(({id}) => id)}</span>
</td>
<td className="mdl-data-table__cell--non-numeric">
{row.created_at}
</td>
<td className="mdl-data-table__cell--non-numeric">
<SelectField label={'Select me'} value={row.status || ''}
className={styles.selectField}
label={lang.t('community.status')}
onChange={(status) => onCommenterStatusChange(row.id, status)}>
<Option value={'ACTIVE'}>{lang.t('community.active')}</Option>
<Option value={'BANNED'}>{lang.t('community.banned')}</Option>
</SelectField>
</td>
<td className="mdl-data-table__cell--non-numeric">
<SelectField label={'Select me'} value={row.roles[0] || ''}
className={styles.selectField}
label={lang.t('community.role')}
onChange={(role) => onRoleChange(row.id, role)}>
<Option value={''}>.</Option>
<Option value={'STAFF'}>{lang.t('community.staff')}</Option>
<Option value={'MODERATOR'}>{lang.t('community.moderator')}</Option>
<Option value={'ADMIN'}>{lang.t('community.admin')}</Option>
</SelectField>
</td>
</tr>
))}
</tbody>
</table>
);
@@ -1,5 +1,5 @@
import React from 'react';
import styles from '../Community.css';
import styles from './Community.css';
import ActionButton from './ActionButton';
@@ -0,0 +1,55 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose} from 'react-apollo';
import {modUserFlaggedQuery} from 'coral-admin/src/graphql/queries';
import {banUser, setUserStatus, rejectUsername} from 'coral-admin/src/graphql/mutations';
import {
fetchAccounts,
updateSorting,
newPage,
showBanUserDialog,
hideBanUserDialog,
showSuspendUserDialog,
hideSuspendUserDialog
} from '../../../actions/community';
import Community from '../components/Community';
class CommunityContainer extends Component {
componentWillMount() {
this.props.fetchAccounts({});
}
render() {
return (
<Community {...this.props} />
);
}
}
const mapStateToProps = (state) => ({
community: state.community.toJS()
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
fetchAccounts,
showBanUserDialog,
hideBanUserDialog,
showSuspendUserDialog,
hideSuspendUserDialog,
updateSorting,
newPage,
}, dispatch);
export default compose(
connect(mapStateToProps, mapDispatchToProps),
modUserFlaggedQuery,
banUser,
setUserStatus,
rejectUsername
)(CommunityContainer);
@@ -0,0 +1,37 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose} from 'react-apollo';
import {setRole, setCommenterStatus} from '../../../actions/community';
import Table from '../components/Table';
class TableContainer extends Component {
constructor (props) {
super(props);
}
render () {
return <Table
{...this.props}
onRoleChange={this.props.setRole}
onCommenterStatusChange={this.props.setCommenterStatus}
commenters={this.props.commenters}
/>;
}
}
const mapStateToProps = (state) => ({
commenters: state.community.get('accounts'),
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
setCommenterStatus,
setRole,
}, dispatch);
export default compose(
connect(mapStateToProps, mapDispatchToProps),
)(TableContainer);
@@ -0,0 +1,2 @@
export {default as Community} from './containers/Community';
export {default as CommunityLayout} from './components/CommunityLayout';
@@ -1,12 +1,4 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {
fetchSettings,
updateSettings,
saveSettingsToServer,
updateWordlist,
updateDomainlist
} from '../../actions/settings';
import {Button, List, Item, Card, Spinner} from 'coral-ui';
import styles from './Configure.css';
@@ -17,45 +9,38 @@ import ModerationSettings from './ModerationSettings';
import TechSettings from './TechSettings';
import {can} from 'coral-framework/services/perms';
class Configure extends Component {
constructor (props) {
super(props);
const lang = new I18n(translations);
this.state = {
activeSection: 'stream',
changed: false,
errors: {}
};
export default class Configure extends Component {
this.changeSection = this.changeSection.bind(this);
}
componentWillMount = () => {
this.props.dispatch(fetchSettings());
}
state = {
activeSection: 'stream',
changed: false,
errors: {}
};
saveSettings = () => {
this.props.dispatch(saveSettingsToServer());
this.props.saveSettingsToServer();
this.setState({changed: false});
}
changeSection(activeSection) {
changeSection = (activeSection) => {
this.setState({activeSection});
}
onChangeWordlist = (listName, list) => {
this.setState({changed: true});
this.props.dispatch(updateWordlist(listName, list));
this.props.updateWordlist(listName, list);
}
onChangeDomainlist = (listName, list) => {
this.setState({changed: true});
this.props.dispatch(updateDomainlist(listName, list));
this.props.updateDomainlist(listName, list);
}
onSettingUpdate = (setting) => {
this.setState({changed: true});
this.props.dispatch(updateSettings(setting));
this.props.updateSettings(setting);
}
// Sets an arbitrary error string and a boolean state.
@@ -176,11 +161,3 @@ class Configure extends Component {
);
}
}
const mapStateToProps = (state) => ({
auth: state.auth.toJS(),
settings: state.settings.toJS()
});
export default connect(mapStateToProps)(Configure);
const lang = new I18n(translations);
@@ -4,7 +4,7 @@ import styles from './Configure.css';
import TagsInput from 'react-tagsinput';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import translations from '../../../translations.json';
const lang = new I18n(translations);
const Domainlist = ({domains, onChangeDomainlist}) => {
@@ -1,9 +1,11 @@
import React, {Component} from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import translations from '../../../translations.json';
import styles from './Configure.css';
import {Button, Card} from 'coral-ui';
const lang = new I18n(translations);
class EmbedLink extends Component {
constructor (props) {
@@ -64,5 +66,3 @@ class EmbedLink extends Component {
}
export default EmbedLink;
const lang = new I18n(translations);
@@ -4,7 +4,7 @@ import {Card} from 'coral-ui';
import {Checkbox} from 'react-mdl';
import Wordlist from './Wordlist';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import translations from '../../../translations.json';
const lang = new I18n(translations);
const updateModeration = (updateSettings, mod) => () => {
@@ -98,7 +98,7 @@ const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => {
&nbsp;
{lang.t('configure.edit-comment-timeframe-text-post')}
</p>
</Card>
</Card>
</div>
);
};
@@ -1,7 +1,7 @@
import React from 'react';
import {SelectField, Option} from 'react-mdl-selectfield';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import translations from '../../../translations.json';
import styles from './Configure.css';
import {Checkbox, Textfield} from 'react-mdl';
import {Card, Icon, TextArea} from 'coral-ui';
@@ -4,7 +4,7 @@ import Domainlist from './Domainlist';
import EmbedLink from './EmbedLink';
import styles from './Configure.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import translations from '../../../translations.json';
const lang = new I18n(translations);
const updateCustomCssUrl = (updateSettings) => (event) => {
@@ -1,6 +1,6 @@
import React from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import translations from '../../../translations.json';
import TagsInput from 'react-tagsinput';
import styles from './Configure.css';
import {Card} from 'coral-ui';
@@ -0,0 +1,42 @@
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 Configure from '../components/Configure';
class ConfigureContainer extends Component {
componentWillMount = () => {
this.props.fetchSettings();
}
render () {
return <Configure {...this.props} />;
}
}
const mapStateToProps = (state) => ({
auth: state.auth.toJS(),
settings: state.settings.toJS()
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
fetchSettings,
updateSettings,
saveSettingsToServer,
updateWordlist,
updateDomainlist
}, dispatch);
export default compose(
connect(mapStateToProps, mapDispatchToProps),
)(ConfigureContainer);
@@ -0,0 +1 @@
export {default} from './containers/Configure';
@@ -1,7 +1,7 @@
import React, {PropTypes} from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations';
import styles from 'coral-admin/src/containers/Dashboard/Dashboard.css';
import styles from './Dashboard.css';
import {Icon} from 'coral-ui';
const lang = new I18n(translations);
@@ -0,0 +1,15 @@
import React from 'react';
import FlagWidget from './FlagWidget';
import ActivityWidget from './ActivityWidget';
import CountdownTimer from './CountdownTimer';
import styles from './Dashboard.css';
export default ({data: {assetsByActivity, assetsByFlag}, reloadData}) => (
<div>
<CountdownTimer handleTimeout={reloadData} />
<div className={styles.Dashboard}>
<FlagWidget assets={assetsByFlag} />
<ActivityWidget assets={assetsByActivity} />
</div>
</div>
);
@@ -0,0 +1,33 @@
import React from 'react';
import {compose} from 'react-apollo';
import {connect} from 'react-redux';
import {getMetrics} from 'coral-admin/src/graphql/queries';
import Dashboard from '../components/Dashboard';
import {Spinner} from 'coral-ui';
class DashboardContainer extends React.Component {
reloadData = () => {
this.props.data.refetch();
}
render () {
if (this.props.data && this.props.data.loading) {
return <Spinner />;
}
return <Dashboard {...this.props} reloadData={this.reloadData} />;
}
}
const mapStateToProps = (state) => {
return {
settings: state.settings.toJS(),
moderation: state.moderation.toJS()
};
};
export default compose(
connect(mapStateToProps),
getMetrics
)(DashboardContainer);
@@ -0,0 +1 @@
export {default} from './containers/Dashboard.js';
@@ -0,0 +1,79 @@
import React, {Component} from 'react';
import styles from './style.css';
import {Wizard, WizardNav} from 'coral-ui';
import Layout from 'coral-admin/src/components/ui/Layout';
import InitialStep from './Steps/InitialStep';
import AddOrganizationName from './Steps/AddOrganizationName';
import CreateYourAccount from './Steps/CreateYourAccount';
import PermittedDomainsStep from './Steps/PermittedDomainsStep';
import FinalStep from './Steps/FinalStep';
export default class Install extends Component {
handleDomainsChange = (value) => {
this.props.updatePermittedDomains(value);
};
handleSettingsChange = ({currentTarget: {name, value}}) => {
this.props.updateSettingsFormData(name, value);
};
handleUserChange = ({currentTarget: {name, value}}) => {
this.props.updateUserFormData(name, value);
};
handleSettingsSubmit = (e) => {
e.preventDefault();
this.props.submitSettings();
};
handleUserSubmit = (e) => {
e.preventDefault();
this.props.submitUser();
}
render() {
const {install} = this.props;
return (
<Layout restricted={true}>
<div className={styles.Install}>
{
!install.alreadyInstalled ? (
<div>
<h2>Welcome to the Coral Project</h2>
{ install.step !== 0 ? <WizardNav items={install.navItems} currentStep={install.step} icon='check'/> : null }
<Wizard
currentStep={install.step}
nextStep={this.props.nextStep}
previousStep={this.props.previousStep}
goToStep={this.props.goToStep}
>
<InitialStep/>
<AddOrganizationName
install={install}
handleSettingsChange={this.handleSettingsChange}
handleSettingsSubmit={this.handleSettingsSubmit}
/>
<CreateYourAccount
install={install}
handleUserChange={this.handleUserChange}
handleUserSubmit={this.handleUserSubmit}
/>
<PermittedDomainsStep
install={install}
handleDomainsChange={this.handleDomainsChange}
finishInstall={this.finishInstall}
/>
<FinalStep/>
</Wizard>
</div>
) : (
<div>Talk is already installed</div>
)
}
</div>
</Layout>
);
}
}
@@ -3,7 +3,7 @@ import styles from './style.css';
import {TextField, Button} from 'coral-ui';
const lang = new I18n(translations);
import translations from '../../translations.json';
import translations from '../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
const AddOrganizationName = (props) => {
@@ -3,7 +3,7 @@ import styles from './style.css';
import {TextField, Button, Spinner} from 'coral-ui';
const lang = new I18n(translations);
import translations from '../../translations.json';
import translations from '../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
const InitialStep = (props) => {
@@ -4,7 +4,7 @@ import {Button} from 'coral-ui';
import {Link} from 'react-router';
const lang = new I18n(translations);
import translations from '../../translations.json';
import translations from '../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
const InitialStep = () => {
@@ -3,7 +3,7 @@ import styles from './style.css';
import {Button} from 'coral-ui';
const lang = new I18n(translations);
import translations from '../../translations.json';
import translations from '../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
const InitialStep = (props) => {
@@ -4,7 +4,7 @@ import {Button, Card} from 'coral-ui';
import TagsInput from 'react-tagsinput';
const lang = new I18n(translations);
import translations from '../../translations.json';
import translations from '../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
const PermittedDomainsStep = (props) => {
@@ -0,0 +1,57 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose} from 'react-apollo';
import Install from '../components/Install';
import {
goToStep,
nextStep,
submitUser,
checkInstall,
previousStep,
finishInstall,
submitSettings,
updateUserFormData,
updateSettingsFormData,
updatePermittedDomains
} from '../../../actions/install';
class InstallContainer extends Component {
componentDidMount() {
const {checkInstall} = this.props;
checkInstall(() => {
this.context.router.push('/admin');
});
}
render() {
return <Install {...this.props}/>;
}
}
InstallContainer.contextTypes = {
router: React.PropTypes.object
};
const mapStateToProps = (state) => ({
install: state.install.toJS()
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
goToStep,
nextStep,
submitUser,
checkInstall,
previousStep,
finishInstall,
submitSettings,
updateUserFormData,
updateSettingsFormData,
updatePermittedDomains
}, dispatch);
export default compose(
connect(mapStateToProps, mapDispatchToProps),
)(InstallContainer);
@@ -0,0 +1 @@
export {default} from './containers/InstallContainer.js';
@@ -1,44 +1,22 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose} from 'react-apollo';
import * as notification from 'coral-admin/src/services/notification';
import key from 'keymaster';
import isEqual from 'lodash/isEqual';
import styles from './components/styles.css';
import styles from './styles.css';
import translations from 'coral-admin/src/translations';
import I18n from 'coral-framework/modules/i18n/i18n';
import {modQueueQuery, getQueueCounts} from '../../graphql/queries';
import {banUser, setCommentStatus, suspendUser} from '../../graphql/mutations';
import {fetchSettings} from 'actions/settings';
import {updateAssets} from 'actions/assets';
import {
toggleModal,
singleView,
showBanUserDialog,
hideBanUserDialog,
showSuspendUserDialog,
hideSuspendUserDialog,
hideShortcutsNote,
viewUserDetail,
hideUserDetail
} from 'actions/moderation';
import {Spinner} from 'coral-ui';
import BanUserDialog from './components/BanUserDialog';
import SuspendUserDialog from './components/SuspendUserDialog';
import BanUserDialog from './BanUserDialog';
import SuspendUserDialog from './SuspendUserDialog';
import ModerationQueue from './ModerationQueue';
import ModerationMenu from './components/ModerationMenu';
import ModerationHeader from './components/ModerationHeader';
import NotFoundAsset from './components/NotFoundAsset';
import ModerationKeysModal from '../../components/ModerationKeysModal';
import ModerationMenu from './ModerationMenu';
import ModerationHeader from './ModerationHeader';
import NotFoundAsset from './NotFoundAsset';
import ModerationKeysModal from '../../../components/ModerationKeysModal';
import UserDetail from './UserDetail';
const lang = new I18n(translations);
class ModerationContainer extends Component {
export default class Moderation extends Component {
state = {
selectedIndex: 0,
sort: 'REVERSE_CHRONOLOGICAL'
@@ -47,7 +25,6 @@ class ModerationContainer extends Component {
componentWillMount() {
const {toggleModal, singleView} = this.props;
this.props.fetchSettings();
key('s', () => singleView());
key('shift+/', () => toggleModal(true));
key('esc', () => toggleModal(false));
@@ -57,6 +34,10 @@ class ModerationContainer extends Component {
key('t', this.moderate(true));
}
onClose = () => {
this.toggleModal(false);
}
moderate = (accept) => () => {
const {acceptComment, rejectComment} = this.props;
const {selectedIndex} = this.state;
@@ -148,28 +129,13 @@ class ModerationContainer extends Component {
}
}
componentWillReceiveProps(nextProps) {
const {updateAssets} = this.props;
if(!isEqual(nextProps.data.assets, this.props.data.assets)) {
updateAssets(nextProps.data.assets);
}
}
render () {
const {data, moderation, settings, assets, onClose, viewUserDetail, hideUserDetail, ...props} = this.props;
const {data, moderation, settings, assets, viewUserDetail, hideUserDetail, ...props} = this.props;
const providedAssetId = this.props.params.id;
const activeTab = this.props.route.path === ':id' ? 'premod' : this.props.route.path;
let asset;
if (!('premodCount' in data)) {
return <div><Spinner/></div>;
}
if (data.error) {
return <div>Error</div>;
}
if (providedAssetId) {
asset = assets.find((asset) => asset.id === this.props.params.id);
@@ -253,7 +219,7 @@ class ModerationContainer extends Component {
hideShortcutsNote={props.hideShortcutsNote}
shortcutsNoteVisible={moderation.shortcutsNoteVisible}
open={moderation.modalOpen}
onClose={onClose}/>
onClose={this.onClose}/>
{moderation.userDetailId && (
<UserDetail
id={moderation.userDetailId}
@@ -264,35 +230,3 @@ class ModerationContainer extends Component {
}
}
const mapStateToProps = (state) => ({
moderation: state.moderation.toJS(),
settings: state.settings.toJS(),
auth: state.auth.toJS(),
assets: state.assets.get('assets')
});
const mapDispatchToProps = (dispatch) => ({
onClose: () => dispatch(toggleModal(false)),
hideBanUserDialog: () => dispatch(hideBanUserDialog(false)),
...bindActionCreators({
toggleModal,
singleView,
updateAssets,
fetchSettings,
showBanUserDialog,
hideShortcutsNote,
showSuspendUserDialog,
hideSuspendUserDialog,
viewUserDetail,
hideUserDetail,
}, dispatch),
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
setCommentStatus,
getQueueCounts,
banUser,
suspendUser,
modQueueQuery,
)(ModerationContainer);
@@ -1,12 +1,12 @@
import React, {PropTypes} from 'react';
import Comment from './components/Comment';
import styles from './components/styles.css';
import EmptyCard from '../../components/EmptyCard';
import {actionsMap} from './helpers/moderationQueueActionsMap';
import Comment from './Comment';
import styles from './styles.css';
import EmptyCard from '../../../components/EmptyCard';
import {actionsMap} from '../helpers/moderationQueueActionsMap';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations';
import LoadMore from './components/LoadMore';
import LoadMore from './LoadMore';
const lang = new I18n(translations);
class ModerationQueue extends React.Component {
@@ -1,14 +1,13 @@
import React, {PropTypes} from 'react';
import {Button, Drawer} from 'coral-ui';
import styles from './UserDetail.css';
import {compose} from 'react-apollo';
import {getUserDetail} from 'coral-admin/src/graphql/queries';
import Slot from 'coral-framework/components/Slot';
class UserDetail extends React.Component {
export default class UserDetail extends React.Component {
static propTypes = {
id: PropTypes.string.isRequired,
hideUserDetail: PropTypes.func.isRequired
hideUserDetail: PropTypes.func.isRequired,
data: PropTypes.string.isRequired,
}
copyPermalink = () => {
@@ -69,6 +68,3 @@ class UserDetail extends React.Component {
}
}
export default compose(
getUserDetail
)(UserDetail);
@@ -0,0 +1,83 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose} from 'react-apollo';
import isEqual from 'lodash/isEqual';
import {modQueueQuery, getQueueCounts} from '../../../graphql/queries';
import {banUser, setCommentStatus, suspendUser} from '../../../graphql/mutations';
import {fetchSettings} from 'actions/settings';
import {updateAssets} from 'actions/assets';
import {
toggleModal,
singleView,
showBanUserDialog,
hideBanUserDialog,
showSuspendUserDialog,
hideSuspendUserDialog,
hideShortcutsNote,
viewUserDetail,
hideUserDetail
} from 'actions/moderation';
import {Spinner} from 'coral-ui';
import Moderation from '../components/Moderation';
class ModerationContainer extends Component {
componentWillMount() {
this.props.fetchSettings();
}
componentWillReceiveProps(nextProps) {
const {updateAssets} = this.props;
if(!isEqual(nextProps.data.assets, this.props.data.assets)) {
updateAssets(nextProps.data.assets);
}
}
render () {
const {data} = this.props;
if (!('premodCount' in data)) {
return <div><Spinner/></div>;
}
if (data.error) {
return <div>Error</div>;
}
return <Moderation {...this.props} />;
}
}
const mapStateToProps = (state) => ({
moderation: state.moderation.toJS(),
settings: state.settings.toJS(),
auth: state.auth.toJS(),
assets: state.assets.get('assets')
});
const mapDispatchToProps = (dispatch) => ({
...bindActionCreators({
toggleModal,
singleView,
updateAssets,
fetchSettings,
showBanUserDialog,
hideBanUserDialog,
hideShortcutsNote,
showSuspendUserDialog,
hideSuspendUserDialog,
viewUserDetail,
hideUserDetail,
}, dispatch),
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
setCommentStatus,
getQueueCounts,
banUser,
suspendUser,
modQueueQuery,
)(ModerationContainer);
@@ -0,0 +1,19 @@
import React, {PropTypes} from 'react';
import {compose} from 'react-apollo';
import {getUserDetail} from 'coral-admin/src/graphql/queries';
import UserDetail from '../components/UserDetail';
class UserDetailContainer extends React.Component {
static propTypes = {
id: PropTypes.string.isRequired,
hideUserDetail: PropTypes.func.isRequired
}
render () {
return <UserDetail {...this.props}/>;
}
}
export default compose(
getUserDetail
)(UserDetailContainer);
@@ -0,0 +1,2 @@
export {default as Moderation} from './containers/Moderation';
export {default as ModerationLayout} from './components/ModerationLayout';
@@ -1,8 +1,6 @@
import React, {Component} from 'react';
import styles from './Stories.css';
import {connect} from 'react-redux';
import I18n from 'coral-framework/modules/i18n/i18n';
import {fetchAssets, updateAssetState} from 'coral-admin/src/actions/assets';
import translations from 'coral-admin/src/translations.json';
import {Link} from 'react-router';
@@ -11,9 +9,11 @@ import {DataTable, TableHeader, RadioGroup, Radio} from 'react-mdl';
import EmptyCard from 'coral-admin/src/components/EmptyCard';
import sortBy from 'lodash/sortBy';
const lang = new I18n(translations);
const limit = 25;
class Stories extends Component {
export default class Stories extends Component {
state = {
search: '',
@@ -171,22 +171,3 @@ class Stories extends Component {
);
}
}
const mapStateToProps = ({assets}) => {
return {
assets: assets.toJS()
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchAssets: (...args) => {
dispatch(fetchAssets.apply(this, args));
},
updateAssetState: (...args) => dispatch(updateAssetState.apply(this, args))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Stories);
const lang = new I18n(translations);
@@ -0,0 +1,27 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose} from 'react-apollo';
import {fetchAssets, updateAssetState} from 'coral-admin/src/actions/assets';
import Stories from '../components/Stories';
class StoriesContainer extends Component {
render () {
return <Stories {...this.props} />;
}
}
const mapStateToProps = (state) => ({
assets: state.assets.toJS()
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
fetchAssets,
updateAssetState,
}, dispatch);
export default compose(
connect(mapStateToProps, mapDispatchToProps),
)(StoriesContainer);
@@ -0,0 +1 @@
export {default} from './containers/Stories.js';
+4 -1
View File
@@ -381,12 +381,15 @@ module.exports = class UsersService {
* @param {Function} done callback after the operation is complete
*/
static addRoleToUser(id, role) {
const roles = [];
// Check to see if the user role is in the allowable set of roles.
if (USER_ROLES.indexOf(role) === -1) {
if (role && USER_ROLES.indexOf(role) === -1) {
// User role is not supported! Error out here.
return Promise.reject(new Error(`role ${role} is not supported`));
} else {
roles.push(role);
}
return UserModel.update({id}, {$set: {roles: [role]}});