diff --git a/src/components/modal/base/modal.scss b/src/components/modal/base/modal.scss index 41a4a4812..bd71751e1 100644 --- a/src/components/modal/base/modal.scss +++ b/src/components/modal/base/modal.scss @@ -84,6 +84,12 @@ row to appear to contain overflow. */ margin-bottom: .9375rem; } +/* For action button row where left/right margin is handled by parent element */ +.action-buttons.action-buttons-no-inset { + margin-left: 0; + margin-right: 0; +} + .action-button { margin: 0 0 0 .54625rem; border-radius: .25rem; diff --git a/src/components/modal/report/form-step.jsx b/src/components/modal/report/form-step.jsx new file mode 100644 index 000000000..7d8b14fdf --- /dev/null +++ b/src/components/modal/report/form-step.jsx @@ -0,0 +1,89 @@ +const bindAll = require('lodash.bindall'); +const PropTypes = require('prop-types'); +const React = require('react'); +const FormattedMessage = require('react-intl').FormattedMessage; +const classNames = require('classnames'); + +const Form = require('../../forms/form.jsx'); +const Button = require('../../forms/button.jsx'); +const Spinner = require('../../spinner/spinner.jsx'); +const FlexRow = require('../../flex-row/flex-row.jsx'); +require('../../forms/button.scss'); + +/** + * Step to be used in a form progression. Provides wrapping form element, + * renders children input elements, then provides a next button row + * that responds to form validation and submission spinner. + */ +class FormStep extends React.Component { + constructor (props) { + super(props); + this.state = { + valid: false + }; + bindAll(this, [ + 'handleValid', + 'handleInvalid' + ]); + } + handleValid () { + this.setState({valid: true}); + } + handleInvalid () { + this.setState({valid: false}); + } + render () { + const {onNext, children, isWaiting, nextLabel} = this.props; + // Submit button is enabled if form isn't already submitting, and either the form passes validation, + // or the submitEnabled prop is true. This lets submitEnabled prop override validation. + const submitEnabled = (this.props.submitEnabled || this.state.valid) && !isWaiting; + const submitDisabledParam = submitEnabled ? {} : {disabled: 'disabled'}; + return ( +
+ ); + } +} + +FormStep.propTypes = { + children: PropTypes.node.isRequired, + isWaiting: PropTypes.bool, + nextLabel: PropTypes.shape({id: PropTypes.string.isRequired}).isRequired, + onNext: PropTypes.func.isRequired, + submitEnabled: PropTypes.bool +}; + +FormStep.defaultProps = { + isWaiting: false, + submitEnabled: false +}; + +module.exports = FormStep; diff --git a/src/components/modal/report/modal.jsx b/src/components/modal/report/modal.jsx index 85705650c..9ff95c7f1 100644 --- a/src/components/modal/report/modal.jsx +++ b/src/components/modal/report/modal.jsx @@ -6,95 +6,50 @@ const FormattedMessage = require('react-intl').FormattedMessage; const injectIntl = require('react-intl').injectIntl; const intlShape = require('react-intl').intlShape; const Modal = require('../base/modal.jsx'); -const classNames = require('classnames'); const ModalTitle = require('../base/modal-title.jsx'); const ModalInnerContent = require('../base/modal-inner-content.jsx'); -const Form = require('../../forms/form.jsx'); -const Button = require('../../forms/button.jsx'); const Select = require('../../forms/select.jsx'); -const Spinner = require('../../spinner/spinner.jsx'); const TextArea = require('../../forms/textarea.jsx'); -const FlexRow = require('../../flex-row/flex-row.jsx'); const previewActions = require('../../../redux/preview.js'); +const Progression = require('../../progression/progression.jsx'); +const FormStep = require('./form-step.jsx'); +const {reportOptionsShape, REPORT_OPTIONS} = require('./report-options.js'); require('../../forms/button.scss'); require('./modal.scss'); -const REPORT_OPTIONS = [ - { - value: '', - label: {id: 'report.reasonPlaceHolder'}, - prompt: {id: 'report.promptPlaceholder'} - }, - { - value: '0', - label: {id: 'report.reasonCopy'}, - prompt: {id: 'report.promptCopy'} - }, - { - value: '1', - label: {id: 'report.reasonUncredited'}, - prompt: {id: 'report.promptUncredited'} - }, - { - value: '2', - label: {id: 'report.reasonScary'}, - prompt: {id: 'report.promptScary'} - }, - { - value: '3', - label: {id: 'report.reasonLanguage'}, - prompt: {id: 'report.promptLanguage'} - }, - { - value: '4', - label: {id: 'report.reasonMusic'}, - prompt: {id: 'report.promptMusic'} - }, - { - value: '8', - label: {id: 'report.reasonImage'}, - prompt: {id: 'report.promptImage'} - }, - { - value: '5', - label: {id: 'report.reasonPersonal'}, - prompt: {id: 'report.promptPersonal'} - }, - { - value: '6', - label: {id: 'general.other'}, - prompt: {id: 'report.promptGuidelines'} - } -]; +// The Progression component uses numbers to track which step it's on, but that's +// hard to read. Make the code easier to read by giving each step number a label. +const STEPS = { + category: 0, + textInput: 1, + confirmation: 2 +}; class ReportModal extends React.Component { constructor (props) { super(props); bindAll(this, [ - 'handleCategorySelect', - 'handleValid', - 'handleInvalid' + 'handleSetCategory', + 'handleSubmit' ]); this.state = { - category: '', - notes: '', - valid: false + step: STEPS.category, + categoryValue: '' }; } - handleCategorySelect (name, value) { - this.setState({category: value}); + handleSetCategory (formData) { + return this.setState({ + categoryValue: formData.category, + step: STEPS.textInput + }); } - handleValid () { - this.setState({valid: true}); - } - handleInvalid () { - this.setState({valid: false}); - } - lookupPrompt (value) { - const prompt = REPORT_OPTIONS.find(item => item.value === value).prompt; - return this.props.intl.formatMessage(prompt); + handleSubmit (formData) { + this.props.onReport({ + report_category: this.state.categoryValue, + notes: formData.notes + }); } render () { const { @@ -103,14 +58,19 @@ class ReportModal extends React.Component { isError, isOpen, isWaiting, - onReport, // eslint-disable-line no-unused-vars onRequestClose, type, + reportOptions, ...modalProps } = this.props; - const submitEnabled = this.state.valid && !isWaiting; - const submitDisabledParam = submitEnabled ? {} : {disabled: 'disabled'}; const contentLabel = intl.formatMessage({id: `report.${type}`}); + const categoryRequiredMessage = intl.formatMessage({id: 'report.reasonMissing'}); + const category = reportOptions.find(o => o.value === this.state.categoryValue) || reportOptions[0]; + + // Confirmation step is shown if a report has been submitted, even if state is reset by closing the modal. + // This prevents multiple report submission within the same session because submission is stored in redux. + const step = isConfirmed ? STEPS.confirmation : this.state.step; + return (