mirror of
https://github.com/wassname/talk.git
synced 2026-07-03 23:17:14 +08:00
Merge pull request #1498 from coralproject/#156312019
Admin Settings: Save and discard changes / Routes
This commit is contained in:
@@ -2,10 +2,15 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Router, Route, IndexRedirect, IndexRoute } from 'react-router';
|
||||
|
||||
import Configure from 'routes/Configure';
|
||||
import Install from 'routes/Install';
|
||||
import Stories from 'routes/Stories';
|
||||
import Community from 'routes/Community';
|
||||
|
||||
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 { ModerationLayout, Moderation } from 'routes/Moderation';
|
||||
|
||||
import Layout from 'containers/Layout';
|
||||
@@ -15,7 +20,14 @@ const routes = (
|
||||
<Route exact path="/admin/install" component={Install} />
|
||||
<Route path="/admin" component={Layout}>
|
||||
<IndexRedirect to="/admin/moderate" />
|
||||
<Route path="configure" component={Configure} />
|
||||
|
||||
<Route path="configure" component={Configure}>
|
||||
<Route path="stream" component={StreamSettings} />
|
||||
<Route path="moderation" component={ModerationSettings} />
|
||||
<Route path="tech" component={TechSettings} />
|
||||
<IndexRedirect to="stream" />
|
||||
</Route>
|
||||
|
||||
<Route path="stories" component={Stories} />
|
||||
|
||||
{/* Community Routes */}
|
||||
|
||||
@@ -8,6 +8,10 @@ export const clearPending = () => {
|
||||
return { type: actions.CLEAR_PENDING };
|
||||
};
|
||||
|
||||
export const setActiveSection = section => {
|
||||
return { type: actions.SET_ACTIVE_SECTION, section };
|
||||
export const showSaveDialog = () => {
|
||||
return { type: actions.SHOW_SAVE_DIALOG };
|
||||
};
|
||||
|
||||
export const hideSaveDialog = () => {
|
||||
return { type: actions.HIDE_SAVE_DIALOG };
|
||||
};
|
||||
|
||||
@@ -2,4 +2,6 @@ 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`;
|
||||
|
||||
export const SHOW_SAVE_DIALOG = `${prefix}_SHOW_SAVE_DIALOG`;
|
||||
export const HIDE_SAVE_DIALOG = `${prefix}_HIDE_SAVE_DIALOG`;
|
||||
|
||||
@@ -6,11 +6,23 @@ const initialState = {
|
||||
canSave: false,
|
||||
pending: {},
|
||||
errors: {},
|
||||
activeSection: 'stream',
|
||||
saveDialog: false,
|
||||
};
|
||||
|
||||
export default function configure(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case actions.SHOW_SAVE_DIALOG: {
|
||||
return {
|
||||
...state,
|
||||
saveDialog: true,
|
||||
};
|
||||
}
|
||||
case actions.HIDE_SAVE_DIALOG: {
|
||||
return {
|
||||
...state,
|
||||
saveDialog: false,
|
||||
};
|
||||
}
|
||||
case actions.UPDATE_PENDING: {
|
||||
let next = state;
|
||||
if (action.updater) {
|
||||
@@ -40,11 +52,8 @@ export default function configure(state = initialState, action) {
|
||||
pending: {},
|
||||
canSave: false,
|
||||
};
|
||||
case actions.SET_ACTIVE_SECTION:
|
||||
return {
|
||||
...state,
|
||||
activeSection: action.section,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1,50 +1,37 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Button, List, Item } from 'coral-ui';
|
||||
import styles from './Configure.css';
|
||||
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 React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import { Button, List, Item } from 'coral-ui';
|
||||
import { can } from 'coral-framework/services/perms';
|
||||
import styles from './Configure.css';
|
||||
import SaveChangesDialog from './SaveChangesDialog';
|
||||
|
||||
export default class Configure extends Component {
|
||||
getSectionComponent(section) {
|
||||
switch (section) {
|
||||
case 'stream':
|
||||
return StreamSettings;
|
||||
case 'moderation':
|
||||
return ModerationSettings;
|
||||
case 'tech':
|
||||
return TechSettings;
|
||||
}
|
||||
throw new Error(`Unknown section ${section}`);
|
||||
}
|
||||
|
||||
class Configure extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
currentUser,
|
||||
canSave,
|
||||
savePending,
|
||||
setActiveSection,
|
||||
activeSection,
|
||||
} = this.props;
|
||||
const SectionComponent = this.getSectionComponent(activeSection);
|
||||
const { canSave, currentUser, root, savePending, settings } = this.props;
|
||||
|
||||
if (!can(currentUser, 'UPDATE_CONFIG')) {
|
||||
return (
|
||||
<p>
|
||||
You must be an administrator to access config settings. Please find
|
||||
the nearest Admin and ask them to level you up!
|
||||
</p>
|
||||
);
|
||||
return <p>{t('configure.access_message')}</p>;
|
||||
}
|
||||
|
||||
const passProps = {
|
||||
root,
|
||||
settings,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<SaveChangesDialog
|
||||
saveDialog={this.props.saveDialog}
|
||||
hideSaveDialog={this.props.hideSaveDialog}
|
||||
saveChanges={this.props.saveChanges}
|
||||
discardChanges={this.props.discardChanges}
|
||||
/>
|
||||
<div className={styles.leftColumn}>
|
||||
<List onChange={setActiveSection} activeItem={activeSection}>
|
||||
<List
|
||||
onChange={this.props.handleSectionChange}
|
||||
activeItem={this.props.activeSection}
|
||||
>
|
||||
<Item itemId="stream" icon="speaker_notes">
|
||||
{t('configure.stream_settings')}
|
||||
</Item>
|
||||
@@ -74,10 +61,7 @@ export default class Configure extends Component {
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.mainContent}>
|
||||
<SectionComponent
|
||||
root={this.props.root}
|
||||
settings={this.props.settings}
|
||||
/>
|
||||
{React.cloneElement(this.props.children, passProps)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -86,10 +70,17 @@ export default class Configure extends Component {
|
||||
|
||||
Configure.propTypes = {
|
||||
savePending: PropTypes.func.isRequired,
|
||||
saveChanges: PropTypes.func.isRequired,
|
||||
discardChanges: PropTypes.func.isRequired,
|
||||
currentUser: PropTypes.object.isRequired,
|
||||
root: PropTypes.object.isRequired,
|
||||
settings: PropTypes.object.isRequired,
|
||||
canSave: PropTypes.bool.isRequired,
|
||||
setActiveSection: PropTypes.func.isRequired,
|
||||
handleSectionChange: PropTypes.func.isRequired,
|
||||
activeSection: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
saveDialog: PropTypes.bool,
|
||||
hideSaveDialog: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Configure;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
.buttonActions {
|
||||
padding-top: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
padding: 25px;
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
.close {
|
||||
font-size: 20px;
|
||||
line-height: 14px;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
position: absolute;
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
color: #363636;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.cancel {
|
||||
color: #363636;
|
||||
margin-right: 15px;
|
||||
display: inline-block;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
margin-left: 5px;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import { Button, Dialog } from 'coral-ui';
|
||||
import styles from './SaveChangesDialog.css';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
const SaveChangesDialog = ({
|
||||
saveDialog,
|
||||
hideSaveDialog,
|
||||
saveChanges,
|
||||
discardChanges,
|
||||
}) => (
|
||||
<Dialog
|
||||
className={cn(styles.dialog, 'talk-admin-configure-save-dialog')}
|
||||
id="saveDialog"
|
||||
open={saveDialog}
|
||||
onCancel={hideSaveDialog}
|
||||
>
|
||||
<span className={styles.close} onClick={hideSaveDialog}>
|
||||
×
|
||||
</span>
|
||||
<div className={styles.title}>
|
||||
{t('configure.save_changes_dialog.unsaved_changes')}
|
||||
</div>
|
||||
{t('configure.save_changes_dialog.copy')}
|
||||
<div
|
||||
className={cn(
|
||||
styles.buttonActions,
|
||||
'talk-admin-configure-save-dialog-button-actions'
|
||||
)}
|
||||
>
|
||||
<a className={styles.cancel} onClick={hideSaveDialog}>
|
||||
Cancel
|
||||
</a>
|
||||
<Button onClick={discardChanges} className={styles.button}>
|
||||
{t('configure.save_changes_dialog.discard')}
|
||||
</Button>
|
||||
<Button onClick={saveChanges} cStyle="green" className={styles.button}>
|
||||
{t('configure.save_changes_dialog.save_settings')}
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
SaveChangesDialog.propTypes = {
|
||||
saveDialog: PropTypes.bool.isRequired,
|
||||
hideSaveDialog: PropTypes.func.isRequired,
|
||||
saveChanges: PropTypes.func.isRequired,
|
||||
discardChanges: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default SaveChangesDialog;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
@@ -10,15 +10,70 @@ import { getDefinitionName } from 'coral-framework/utils';
|
||||
import StreamSettings from './StreamSettings';
|
||||
import TechSettings from './TechSettings';
|
||||
import ModerationSettings from './ModerationSettings';
|
||||
import { clearPending, setActiveSection } from '../../../actions/configure';
|
||||
import {
|
||||
clearPending,
|
||||
showSaveDialog,
|
||||
hideSaveDialog,
|
||||
} from '../../../actions/configure';
|
||||
import Configure from '../components/Configure';
|
||||
import { withRouter } from 'react-router';
|
||||
|
||||
class ConfigureContainer extends React.Component {
|
||||
state = { nextRoute: '' };
|
||||
|
||||
class ConfigureContainer extends Component {
|
||||
savePending = async () => {
|
||||
await this.props.updateSettings(this.props.pending);
|
||||
this.props.clearPending();
|
||||
};
|
||||
|
||||
saveChanges = async () => {
|
||||
await this.savePending();
|
||||
this.props.hideSaveDialog();
|
||||
this.gotoNextRoute();
|
||||
};
|
||||
|
||||
discardChanges = async () => {
|
||||
await this.props.clearPending();
|
||||
this.props.hideSaveDialog();
|
||||
this.gotoNextRoute();
|
||||
};
|
||||
|
||||
gotoNextRoute = () => {
|
||||
const { nextRoute } = this.state;
|
||||
if (nextRoute) {
|
||||
this.props.router.push(nextRoute);
|
||||
this.setState({ nextRoute: '' });
|
||||
}
|
||||
};
|
||||
|
||||
handleSectionChange = async section => {
|
||||
const nextRoute = `/admin/configure/${section}`;
|
||||
|
||||
if (this.shouldShowSaveDialog()) {
|
||||
await this.setState({ nextRoute });
|
||||
this.props.showSaveDialog();
|
||||
} else {
|
||||
// Just go to the section
|
||||
this.props.router.push(nextRoute);
|
||||
}
|
||||
};
|
||||
|
||||
shouldShowSaveDialog = () => {
|
||||
return !!Object.keys(this.props.pending).length;
|
||||
};
|
||||
|
||||
routeLeave = ({ pathname }) => {
|
||||
if (this.shouldShowSaveDialog()) {
|
||||
this.setState({ nextRoute: pathname });
|
||||
this.props.showSaveDialog();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.router.setRouteLeaveHook(this.props.route, this.routeLeave);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.data.error) {
|
||||
return <div>{this.props.data.error.message}</div>;
|
||||
@@ -30,14 +85,20 @@ class ConfigureContainer extends Component {
|
||||
|
||||
return (
|
||||
<Configure
|
||||
saveChanges={this.saveChanges}
|
||||
discardChanges={this.discardChanges}
|
||||
saveDialog={this.props.saveDialog}
|
||||
activeSection={this.props.routes[3].path}
|
||||
hideSaveDialog={this.props.hideSaveDialog}
|
||||
canSave={this.props.canSave}
|
||||
currentUser={this.props.currentUser}
|
||||
root={this.props.root}
|
||||
settings={this.props.mergedSettings}
|
||||
canSave={this.props.canSave}
|
||||
handleSectionChange={this.handleSectionChange}
|
||||
savePending={this.savePending}
|
||||
setActiveSection={this.props.setActiveSection}
|
||||
activeSection={this.props.activeSection}
|
||||
/>
|
||||
>
|
||||
{this.props.children}
|
||||
</Configure>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -74,18 +135,21 @@ const mapStateToProps = state => ({
|
||||
pending: state.configure.pending,
|
||||
canSave: state.configure.canSave,
|
||||
activeSection: state.configure.activeSection,
|
||||
saveDialog: state.configure.saveDialog,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
clearPending,
|
||||
setActiveSection,
|
||||
showSaveDialog,
|
||||
hideSaveDialog,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
withUpdateSettings,
|
||||
withConfigureQuery,
|
||||
@@ -93,14 +157,20 @@ export default compose(
|
||||
)(ConfigureContainer);
|
||||
|
||||
ConfigureContainer.propTypes = {
|
||||
activeSection: PropTypes.string,
|
||||
updateSettings: PropTypes.func.isRequired,
|
||||
clearPending: PropTypes.func.isRequired,
|
||||
setActiveSection: PropTypes.func.isRequired,
|
||||
showSaveDialog: PropTypes.func.isRequired,
|
||||
hideSaveDialog: PropTypes.func.isRequired,
|
||||
saveDialog: PropTypes.bool.isRequired,
|
||||
currentUser: PropTypes.object.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
root: PropTypes.object.isRequired,
|
||||
canSave: PropTypes.bool.isRequired,
|
||||
pending: PropTypes.object.isRequired,
|
||||
mergedSettings: PropTypes.object.isRequired,
|
||||
activeSection: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
router: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
routes: PropTypes.array,
|
||||
};
|
||||
|
||||
@@ -154,12 +154,19 @@ en:
|
||||
sign_out: "Sign Out"
|
||||
stories: Stories
|
||||
stream_settings: "Stream Settings"
|
||||
access_message: "You must be an administrator to access config settings. Please find the nearest Admin and ask them to level you up!"
|
||||
suspect_word_title: "Suspect words list"
|
||||
suspect_word_text: "Comments which contain these words or phrases (not case-sensitive) will be highlighted in the comment stream. Type a word and press Enter or Tab to add. Optionally paste a comma-separated list."
|
||||
tech_settings: "Tech Settings"
|
||||
title: "Configure Comment Stream"
|
||||
weeks: Weeks
|
||||
wordlist: "Banned Words"
|
||||
save_changes_dialog:
|
||||
unsaved_changes: "Unsaved changes"
|
||||
copy: "You have made one or more changes without saving. Would you like to save or discard your changes?"
|
||||
save_settings: "Save Settings"
|
||||
discard: "Discard"
|
||||
cancel: "Cancel"
|
||||
continue: "Continue"
|
||||
createdisplay:
|
||||
check_the_form: "Invalid Form. Please check the fields"
|
||||
|
||||
@@ -153,12 +153,19 @@ es:
|
||||
sign_out: "Desconectar"
|
||||
stories: Artículos
|
||||
stream_settings: "Configuración de Comentarios"
|
||||
access_message: "Usted debe ser un administrador para acceder a esta página. Encuentre a otro admin y actualice los permisos de su cuenta!"
|
||||
suspect_word_title: "Lista de palabras sospechosas"
|
||||
suspect_word_text: "Comentarios que contengan estas palabras o frases, considerando mayusculas y minúsculas, serán automáticamente destacadas en los comentarios publicados. Escribir una palabra y apretar Enter o Tabulador para agregarla. O pegar una lista de palabras separadas por coma."
|
||||
tech_settings: "Configuración Técnica"
|
||||
title: "Configurar los comentarios"
|
||||
weeks: Semanas
|
||||
wordlist: "Palabras Suspendidas"
|
||||
save_changes_dialog:
|
||||
unsaved_changes: "Cambios no guardados"
|
||||
copy: Has hecho uno o más cambios sin guardar. Deseas guardar o descartar tus cambios?"
|
||||
save_settings: "Guardar configuración"
|
||||
discard: "Descartar"
|
||||
cancel: "Cancelar"
|
||||
continue: "Continuar"
|
||||
createdisplay:
|
||||
check_the_form: "Formulario Inválido. Por favor verifica los campos"
|
||||
|
||||
Reference in New Issue
Block a user