From bc3a454f4c1d605e57c04c35e4f95be789d5336a Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 12 Mar 2020 14:59:57 -0400 Subject: [PATCH 1/3] Split project report flow into multiple steps --- src/components/modal/report/form-step.jsx | 89 ++++++ src/components/modal/report/modal.jsx | 292 +++++++----------- src/components/modal/report/modal.scss | 9 +- src/components/modal/report/report-options.js | 77 +++++ 4 files changed, 277 insertions(+), 190 deletions(-) create mode 100644 src/components/modal/report/form-step.jsx create mode 100644 src/components/modal/report/report-options.js diff --git a/src/components/modal/report/form-step.jsx b/src/components/modal/report/form-step.jsx new file mode 100644 index 000000000..bfd4b15a3 --- /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; + // Allow submit to be force-enabled by submitEnabled prop. Otherwise use form validation, + // with default being not-submittable. + const submitEnabled = (this.props.submitEnabled || this.state.valid) && !isWaiting; + const submitDisabledParam = submitEnabled ? {} : {disabled: 'disabled'}; + return ( +
+ {children} + + + +
+ ); + } +} + +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..d4038eb95 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'} - } -]; +// Progression component only addresses steps by number, but this flow +// may skip steps so the code is easier to read with a map. +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,14 @@ 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]; return ( - -
- - {isConfirmed ? ( -
-
+ + {isError && ( +
+ +
+ )} + + +
+ + + + ) + }} + /> +
+