From eced7ec7a619f81388ff6b096d42a6c9220e03f4 Mon Sep 17 00:00:00 2001 From: Igor Poplavsky Date: Thu, 28 May 2020 14:40:51 +0300 Subject: [PATCH 1/5] #25: Implemented option for conditional settings --- src/components/layouts/pages/upload/Upload.js | 54 ++++++++--- .../pages/upload/dropzone/Dropzone.scss | 1 - .../settingsCheckbox/SettingsCheckbox.js | 93 +++++++++++++++++++ src/components/shared/checkbox/Checkbox.js | 32 +++++++ .../shared/pageNavigation/PageNavigation.js | 30 +++--- .../shared/pageNavigation/PageNavigation.scss | 1 + src/components/shared/stepper/Stepper.js | 69 +++++++++----- src/store/application/actions.js | 2 + src/store/application/reducer.js | 23 ++++- src/store/application/selectors.js | 2 + src/store/constants.js | 1 + src/store/job/settings/actions.js | 6 ++ src/store/validationProfiles/selectors.js | 2 + 13 files changed, 264 insertions(+), 52 deletions(-) create mode 100644 src/components/layouts/pages/upload/settingsCheckbox/SettingsCheckbox.js create mode 100644 src/components/shared/checkbox/Checkbox.js diff --git a/src/components/layouts/pages/upload/Upload.js b/src/components/layouts/pages/upload/Upload.js index ad269b36..75b047a7 100644 --- a/src/components/layouts/pages/upload/Upload.js +++ b/src/components/layouts/pages/upload/Upload.js @@ -1,39 +1,67 @@ import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; -import { hasFilesAttached } from '../../../../store/pdfFiles/selectors'; import { connect } from 'react-redux'; +import { Redirect } from 'react-router-dom'; + import AppPages from '../../../AppPages'; +import { validate } from '../../../../store/job/actions'; +import { hasFilesAttached } from '../../../../store/pdfFiles/selectors'; +import { getUseSettings } from '../../../../store/application/selectors'; +import { getJobId } from '../../../../store/job/selectors'; import Dropzone from './dropzone/Dropzone'; import WizardStep from '../../wizardStep/WizardStep'; import PageNavigation from '../../../shared/pageNavigation/PageNavigation'; +import SettingsCheckbox from './settingsCheckbox/SettingsCheckbox'; -function Upload(props) { - const { filesAttached } = props; - const forwardButton = useMemo( - () => ({ - label: 'Configure job', - to: AppPages.SETTINGS, - disabled: !filesAttached, - }), - [filesAttached] - ); +function Upload({ filesAttached, isUseSettings, jobId, onValidateClick }) { + const forwardButton = useMemo(() => { + const button = { disabled: !filesAttached }; + if (isUseSettings) { + return { + ...button, + label: 'Configure job', + to: AppPages.SETTINGS, + }; + } + return { + ...button, + label: 'Validate', + onClick: onValidateClick, + }; + }, [filesAttached, onValidateClick, isUseSettings]); + + if (!isUseSettings && jobId) { + // Once job is initialized and we know its ID redirect to status page to track its progress + return ; + } return ( - + } forward={forwardButton} /> ); } Upload.propTypes = { + jobId: PropTypes.string, + isUseSettings: PropTypes.bool.isRequired, filesAttached: PropTypes.bool.isRequired, + onValidateClick: PropTypes.func.isRequired, }; const mapStateToProps = state => { return { filesAttached: hasFilesAttached(state), + isUseSettings: getUseSettings(state), + jobId: getJobId(state), + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onValidateClick: () => dispatch(validate()), }; }; -export default connect(mapStateToProps)(Upload); +export default connect(mapStateToProps, mapDispatchToProps)(Upload); diff --git a/src/components/layouts/pages/upload/dropzone/Dropzone.scss b/src/components/layouts/pages/upload/dropzone/Dropzone.scss index ceb47fea..0ba71fbe 100644 --- a/src/components/layouts/pages/upload/dropzone/Dropzone.scss +++ b/src/components/layouts/pages/upload/dropzone/Dropzone.scss @@ -11,7 +11,6 @@ &__container { width: 70%; - max-width: 700px; height: 100px; margin: auto; padding: 20px; diff --git a/src/components/layouts/pages/upload/settingsCheckbox/SettingsCheckbox.js b/src/components/layouts/pages/upload/settingsCheckbox/SettingsCheckbox.js new file mode 100644 index 00000000..1594b5af --- /dev/null +++ b/src/components/layouts/pages/upload/settingsCheckbox/SettingsCheckbox.js @@ -0,0 +1,93 @@ +import React, { useCallback, useState } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; + +import { getUseSettings } from '../../../../../store/application/selectors'; +import { getProfile } from '../../../../../store/job/settings/selectors'; +import { getDefaultProfileLabel, getDefaultProfileName } from '../../../../../store/validationProfiles/selectors'; +import { toggleUseSettings } from '../../../../../store/application/actions'; +import { resetProfile } from '../../../../../store/job/settings/actions'; +import Checkbox from '../../../../shared/checkbox/Checkbox'; +import Dialog from '../../../../shared/dialog/Dialog'; + +const CHECK_SETTINGS = 'Use custom validation settings'; + +function SettingsCheckbox({ + isUseSettings, + toggleSettings, + profile, + defaultProfile, + defaultProfileLabel, + resetProfile, +}) { + const [resetSettingsDialogOpened, setResetSettingsDialogOpened] = useState(false); + const onSettingsToggle = useCallback(() => { + if (profile !== defaultProfile && isUseSettings) { + return setResetSettingsDialogOpened(true); + } + return toggleSettings(); + }, [defaultProfile, profile, toggleSettings, isUseSettings]); + + const onResetSettingsClose = useCallback(() => { + setResetSettingsDialogOpened(false); + }, []); + const dialogActions = [ + { + label: 'Cancel', + color: 'primary', + align: 'start', + onClick: onResetSettingsClose, + }, + { + label: 'Reset settings', + color: 'primary', + variant: 'contained', + onClick: () => { + resetProfile(); + toggleSettings(); + onResetSettingsClose(); + }, + }, + ]; + + return ( +
+ + + Proceed? + +
+ ); +} + +SettingsCheckbox.propTypes = { + isUseSettings: PropTypes.bool.isRequired, + profile: PropTypes.string, + defaultProfile: PropTypes.string, + defaultProfileLabel: PropTypes.string, + toggleSettings: PropTypes.func.isRequired, + resetProfile: PropTypes.func.isRequired, +}; + +const mapStateToProps = state => { + return { + isUseSettings: getUseSettings(state), + profile: getProfile(state), + defaultProfile: getDefaultProfileName(state), + defaultProfileLabel: getDefaultProfileLabel(state), + }; +}; + +const mapDispatchToProps = dispatch => { + return { + toggleSettings: () => dispatch(toggleUseSettings()), + resetProfile: () => dispatch(resetProfile()), + }; +}; + +export default connect(mapStateToProps, mapDispatchToProps)(SettingsCheckbox); diff --git a/src/components/shared/checkbox/Checkbox.js b/src/components/shared/checkbox/Checkbox.js new file mode 100644 index 00000000..431dd23a --- /dev/null +++ b/src/components/shared/checkbox/Checkbox.js @@ -0,0 +1,32 @@ +import React, { useCallback } from 'react'; +import PropTypes from 'prop-types'; + +import MaterialCheckbox from '@material-ui/core/Checkbox'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; + +function Checkbox({ checked, label, disabled, onChange }) { + const handleChange = useCallback(e => onChange(e.target.checked), [onChange]); + + return ( + } + /> + ); +} + +Checkbox.propTypes = { + checked: PropTypes.bool.isRequired, + label: PropTypes.string, + disabled: PropTypes.bool, + onChange: PropTypes.func.isRequired, +}; + +Checkbox.defaultProps = { + label: '', + disabled: false, +}; + +export default Checkbox; diff --git a/src/components/shared/pageNavigation/PageNavigation.js b/src/components/shared/pageNavigation/PageNavigation.js index 1c9c8f4f..d65b5e57 100644 --- a/src/components/shared/pageNavigation/PageNavigation.js +++ b/src/components/shared/pageNavigation/PageNavigation.js @@ -19,31 +19,35 @@ function PageNavigation(props) { const { back, forward, center } = props; return ( ); } -function getButton(buttonObject, type) { - if (buttonObject?.to) { +function getComponent(componentObject, type) { + if (React.isValidElement(componentObject)) { + return componentObject; + } + + if (componentObject?.to) { return ( - - {buttonObject.label} + + {componentObject.label} ); } - if (buttonObject?.onClick) { + if (componentObject?.onClick) { return ( ); } @@ -59,8 +63,8 @@ const ButtonInterface = PropTypes.shape({ }); PageNavigation.propTypes = { - back: ButtonInterface, - forward: ButtonInterface, + back: PropTypes.oneOfType([ButtonInterface, PropTypes.element]), + forward: PropTypes.oneOfType([ButtonInterface, PropTypes.element]), }; export default PageNavigation; diff --git a/src/components/shared/pageNavigation/PageNavigation.scss b/src/components/shared/pageNavigation/PageNavigation.scss index e880dca8..3472cb42 100644 --- a/src/components/shared/pageNavigation/PageNavigation.scss +++ b/src/components/shared/pageNavigation/PageNavigation.scss @@ -7,6 +7,7 @@ flex-grow: 1; flex-basis: 0; justify-content: center; + align-items: start; .app-link { color: inherit; diff --git a/src/components/shared/stepper/Stepper.js b/src/components/shared/stepper/Stepper.js index 0af16276..95c7f0da 100644 --- a/src/components/shared/stepper/Stepper.js +++ b/src/components/shared/stepper/Stepper.js @@ -1,38 +1,52 @@ import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; import _ from 'lodash'; + +import AppPages from '../../AppPages'; +import { getUseSettings } from '../../../store/application/selectors'; import MaterialStepper from '@material-ui/core/Stepper'; import Step from '@material-ui/core/Step'; import StepLabel from '@material-ui/core/StepLabel'; import StepIcon from './StepIcon'; -import AppPages from '../../AppPages'; + import './Stepper.scss'; -const STEPS = [ - { - key: AppPages.UPLOAD, - label: 'Upload PDF', - }, - { - key: AppPages.SETTINGS, - label: 'Select settings', - }, - { - key: AppPages.STATUS.route, - label: 'Validation', - }, - { - key: AppPages.RESULTS.route, - label: 'Verification results', - }, -]; +function Stepper({ activeStep, useSettings }) { + const STEPS = useMemo( + () => [ + { + key: AppPages.UPLOAD, + label: 'Upload PDF', + }, + { + key: AppPages.SETTINGS, + label: 'Select settings', + skip: !useSettings, + }, + { + key: AppPages.STATUS.route, + label: 'Validation', + }, + { + key: AppPages.RESULTS.route, + label: 'Verification results', + }, + ], + [useSettings] + ); + const activeIndex = useMemo( + () => + _.findIndex( + STEPS.filter(step => !step.skip), + { key: activeStep } + ), + [STEPS, activeStep] + ); -function Stepper(props) { - const { activeStep } = props; - const activeIndex = useMemo(() => _.findIndex(STEPS, { key: activeStep }), [activeStep]); return ( - {STEPS.map(({ label }) => ( + {STEPS.filter(step => !step.skip).map(({ label }) => ( {label} @@ -43,6 +57,13 @@ function Stepper(props) { Stepper.propTypes = { activeStep: PropTypes.string.isRequired, + useSettings: PropTypes.bool.isRequired, +}; + +const mapStateToProps = state => { + return { + useSettings: getUseSettings(state), + }; }; -export default Stepper; +export default connect(mapStateToProps)(Stepper); diff --git a/src/store/application/actions.js b/src/store/application/actions.js index 5987a70d..f0bcde3d 100644 --- a/src/store/application/actions.js +++ b/src/store/application/actions.js @@ -14,6 +14,8 @@ export const unlockApp = createAction('APP_LOCK_SET', () => false); export const resetApp = createAction('APP_RESET'); +export const toggleUseSettings = createAction('USE_SETTINGS_TOGGLE'); + export const reset = () => async (dispatch, getState) => { const file = getFile(getState()); const profile = getDefaultProfileName(getState()); diff --git a/src/store/application/reducer.js b/src/store/application/reducer.js index ed62889c..bbac6192 100644 --- a/src/store/application/reducer.js +++ b/src/store/application/reducer.js @@ -1,8 +1,24 @@ import { handleActions } from 'redux-actions'; +import { USE_SETTINGS_FLAG } from '../constants'; const DEFAULT_STATE = { initialized: false, locked: false, + useSettings: false, +}; + +export const getDefaultState = () => { + let useSettings = sessionStorage.getItem(USE_SETTINGS_FLAG); + + if (!useSettings) { + return DEFAULT_STATE; + } + + useSettings = JSON.parse(useSettings); + return { + ...DEFAULT_STATE, + useSettings, + }; }; export default handleActions( @@ -10,6 +26,11 @@ export default handleActions( APP_RESET: () => DEFAULT_STATE, APP_STARTUP_FINISH: state => ({ ...state, initialized: true }), APP_LOCK_SET: (state, { payload: locked }) => ({ ...state, locked }), + USE_SETTINGS_TOGGLE: state => { + const useSettings = !state.useSettings; + sessionStorage.setItem(USE_SETTINGS_FLAG, useSettings); + return { ...state, useSettings }; + }, }, - DEFAULT_STATE + getDefaultState() ); diff --git a/src/store/application/selectors.js b/src/store/application/selectors.js index d0a42310..d7bbc6b0 100644 --- a/src/store/application/selectors.js +++ b/src/store/application/selectors.js @@ -5,3 +5,5 @@ const getAppState = state => state.appState; export const isInitialized = createSelector(getAppState, ({ initialized }) => initialized); export const isLocked = createSelector(getAppState, ({ locked }) => locked); + +export const getUseSettings = createSelector(getAppState, ({ useSettings }) => useSettings); diff --git a/src/store/constants.js b/src/store/constants.js index e384b53c..88936770 100644 --- a/src/store/constants.js +++ b/src/store/constants.js @@ -1,5 +1,6 @@ export const JOB_FILE = 'JOB_FILE'; export const JOB_SETTINGS = 'JOB_SETTINGS'; +export const USE_SETTINGS_FLAG = 'USE_SETTINGS_FLAG'; export const JOB_STATUS = { CREATED: 'CREATED', diff --git a/src/store/job/settings/actions.js b/src/store/job/settings/actions.js index afbc8b4c..271846fe 100644 --- a/src/store/job/settings/actions.js +++ b/src/store/job/settings/actions.js @@ -1,5 +1,6 @@ import { createAction } from 'redux-actions'; import { JOB_SETTINGS } from '../../constants'; +import { getDefaultProfileName } from '../../validationProfiles/selectors'; const updateSettings = createAction('SETTINGS_UPDATE'); @@ -7,3 +8,8 @@ export const setSetting = (setting, value) => async (dispatch, getState) => { await dispatch(updateSettings({ [setting]: value })); sessionStorage.setItem(JOB_SETTINGS, JSON.stringify(getState().jobSettings)); }; + +export const resetProfile = () => async (dispatch, getState) => { + const profile = getDefaultProfileName(getState()); + dispatch(updateSettings({ profile })); +}; diff --git a/src/store/validationProfiles/selectors.js b/src/store/validationProfiles/selectors.js index 2ba656ba..a484efb9 100644 --- a/src/store/validationProfiles/selectors.js +++ b/src/store/validationProfiles/selectors.js @@ -17,4 +17,6 @@ export const getProfileOptions = createSelector(getProfiles, profiles => { export const getDefaultProfileName = createSelector(getProfiles, profiles => _.first(profiles)?.profileName); +export const getDefaultProfileLabel = createSelector(getProfiles, profiles => _.first(profiles)?.humanReadableName); + export const getProfilesError = createSelector(getProfiles, profiles => _.get(profiles, 'error')); From 7f0d2d4d21ff849436aea381a0caabdfa4c839dd Mon Sep 17 00:00:00 2001 From: Igor Poplavsky Date: Mon, 1 Jun 2020 10:39:04 +0300 Subject: [PATCH 2/5] #25: Test for settings checkbox Plus fixed old settings because of new checkbox --- src/__tests__/integration/app.test.js | 15 ++++++++++++++- src/__tests__/integration/index.js | 11 +++++++++++ src/__tests__/integration/settings.test.js | 13 ++++++++++++- src/__tests__/integration/upload.test.js | 11 ++++++++++- src/__tests__/integration/validation.test.js | 3 --- 5 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/__tests__/integration/app.test.js b/src/__tests__/integration/app.test.js index 15cfd1c9..c1966089 100644 --- a/src/__tests__/integration/app.test.js +++ b/src/__tests__/integration/app.test.js @@ -1,4 +1,4 @@ -import { integrationTest } from './index'; +import { integrationTest, toggleSettingsCheckbox } from './index'; import Loading from '../../components/layouts/pages/loading/Loading'; const { REACT_APP_VERSION } = process.env; @@ -24,4 +24,17 @@ describe('App', () => { } ) ); + + it( + 'Turn settings on and off', + integrationTest((store, component) => { + expect(component.find('.MuiStepLabel-root')).toHaveLength(3); + + toggleSettingsCheckbox(component, true); + expect(component.find('.MuiStepLabel-root')).toHaveLength(4); + + toggleSettingsCheckbox(component, false); + expect(component.find('.MuiStepLabel-root')).toHaveLength(3); + }) + ); }); diff --git a/src/__tests__/integration/index.js b/src/__tests__/integration/index.js index 642b55c1..4a8a377c 100644 --- a/src/__tests__/integration/index.js +++ b/src/__tests__/integration/index.js @@ -7,6 +7,7 @@ import fs from 'mz/fs'; import App from '../../components/App'; import configureStore from '../../store/rootStore'; +import Checkbox from '@material-ui/core/Checkbox'; import { getInfo as getFileServiceInfo, uploadFile, getFileContent } from '../../services/fileService'; import { getInfo as getJobServiceInfo, createJob, updateJob, executeJob, getJob } from '../../services/jobService'; import { getList as getProfilesList } from '../../services/profiles'; @@ -262,3 +263,13 @@ export const skipLoadingPage = async (store, component) => { export const isFileStored = state => state.pdfFiles.length; export const stepFinished = key => state => getProgress(state).steps.find(({ stepKey }) => stepKey === key)?.completed; + +export const toggleSettingsCheckbox = (component, checked) => { + expect(component.find(Checkbox)).toHaveLength(1); + component + .find(Checkbox) + .props() + .onChange({ target: { checked } }); + component.update(); + expect(component.find(Checkbox).props().checked).toBe(checked); +}; diff --git a/src/__tests__/integration/settings.test.js b/src/__tests__/integration/settings.test.js index 0c8b46c9..0eb812a2 100644 --- a/src/__tests__/integration/settings.test.js +++ b/src/__tests__/integration/settings.test.js @@ -1,4 +1,11 @@ -import { DEFAULT_STARTUP_RESPONSES, TEST_FILE, integrationTest, storeFile, moveNext } from './index'; +import { + DEFAULT_STARTUP_RESPONSES, + TEST_FILE, + integrationTest, + storeFile, + moveNext, + toggleSettingsCheckbox, +} from './index'; import MaterialSelect from '@material-ui/core/Select'; import Settings from '../../components/layouts/pages/settings/Settings'; @@ -20,6 +27,7 @@ describe('Settings', () => { integrationTest( async (store, component) => { await storeFile(component, store); + toggleSettingsCheckbox(component, true); moveNext(component); expect(component.find(Settings)).toHaveLength(1); @@ -39,6 +47,7 @@ describe('Settings', () => { 'Profiles list loaded', integrationTest(async (store, component) => { await storeFile(component, store); + toggleSettingsCheckbox(component, true); moveNext(component); expect(component.find(Settings)).toHaveLength(1); @@ -57,6 +66,7 @@ describe('Settings', () => { integrationTest( async (store, component) => { await storeFile(component, store); + toggleSettingsCheckbox(component, true); moveNext(component); expect(component.find(Settings)).toHaveLength(1); expect(getProfileSelectError(component).text()).toBe( @@ -76,6 +86,7 @@ describe('Settings', () => { 'Profile changed', integrationTest(async (store, component) => { await storeFile(component, store, TEST_FILE); + toggleSettingsCheckbox(component, true); moveNext(component); expect(getProfileValue(store)).toEqual(PROFILE_VALUES[0]); diff --git a/src/__tests__/integration/upload.test.js b/src/__tests__/integration/upload.test.js index 378b3145..b8c541a0 100644 --- a/src/__tests__/integration/upload.test.js +++ b/src/__tests__/integration/upload.test.js @@ -1,4 +1,12 @@ -import { TEST_FILE, integrationTest, getNextStepButton, storeFile, moveBack, moveNext } from './index'; +import { + TEST_FILE, + integrationTest, + getNextStepButton, + storeFile, + moveBack, + moveNext, + toggleSettingsCheckbox, +} from './index'; const EMPTY_DROPZONE_TEXT = 'Drop a PDF file, or click to select a file'; const FAILED_FILE = { @@ -58,6 +66,7 @@ describe('Upload', () => { 'File still attached after going back from next step', integrationTest(async (store, component) => { await storeFile(component, store); + toggleSettingsCheckbox(component, true); moveNext(component); moveBack(component); diff --git a/src/__tests__/integration/validation.test.js b/src/__tests__/integration/validation.test.js index da87d12e..3c218c3c 100644 --- a/src/__tests__/integration/validation.test.js +++ b/src/__tests__/integration/validation.test.js @@ -21,9 +21,6 @@ describe('Validation', () => { integrationTest(async (store, component) => { await storeFile(component, store); - // Move to settings - moveNext(component); - // Click validate button moveNext(component); expect(createJob).toHaveBeenCalledTimes(1); From 7d915365d1f673a8ffc5a2c507729f57147d1744 Mon Sep 17 00:00:00 2001 From: "yuliya.ivaniukovich" Date: Mon, 8 Jun 2020 19:57:18 +0300 Subject: [PATCH 3/5] Make stepper corner points be in the same position Update labels according to mockups --- src/components/shared/stepper/Stepper.js | 81 +++++++++++----------- src/components/shared/stepper/Stepper.scss | 24 +++++++ 2 files changed, 65 insertions(+), 40 deletions(-) diff --git a/src/components/shared/stepper/Stepper.js b/src/components/shared/stepper/Stepper.js index 95c7f0da..9a47bb4f 100644 --- a/src/components/shared/stepper/Stepper.js +++ b/src/components/shared/stepper/Stepper.js @@ -1,5 +1,6 @@ import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; import { connect } from 'react-redux'; import _ from 'lodash'; @@ -12,57 +13,57 @@ import StepIcon from './StepIcon'; import './Stepper.scss'; -function Stepper({ activeStep, useSettings }) { - const STEPS = useMemo( - () => [ - { - key: AppPages.UPLOAD, - label: 'Upload PDF', - }, - { - key: AppPages.SETTINGS, - label: 'Select settings', - skip: !useSettings, - }, - { - key: AppPages.STATUS.route, - label: 'Validation', - }, - { - key: AppPages.RESULTS.route, - label: 'Verification results', - }, - ], - [useSettings] - ); - const activeIndex = useMemo( - () => - _.findIndex( - STEPS.filter(step => !step.skip), - { key: activeStep } - ), - [STEPS, activeStep] - ); +const STEPS = [ + { + key: AppPages.UPLOAD, + label: 'Upload PDF', + }, + { + key: AppPages.SETTINGS, + label: 'Configuration', + }, + { + key: AppPages.STATUS.route, + label: 'Validation', + }, + { + key: AppPages.RESULTS.route, + label: 'Results', + }, +]; + +function Stepper({ activeStep, customizeConfig }) { + const steps = useMemo(() => (customizeConfig ? STEPS : _.reject(STEPS, { key: AppPages.SETTINGS })), [ + customizeConfig, + ]); + const activeIndex = useMemo(() => _.findIndex(steps, { key: activeStep }), [steps, activeStep]); return ( - - {STEPS.filter(step => !step.skip).map(({ label }) => ( - - {label} - - ))} - + <> + + {steps.map(({ label }) => ( + + {label} + + ))} + +

{steps[activeIndex].label}

+ ); } Stepper.propTypes = { activeStep: PropTypes.string.isRequired, - useSettings: PropTypes.bool.isRequired, + customizeConfig: PropTypes.bool.isRequired, }; const mapStateToProps = state => { return { - useSettings: getUseSettings(state), + customizeConfig: getUseSettings(state), // TODO: rename selector/reducer }; }; diff --git a/src/components/shared/stepper/Stepper.scss b/src/components/shared/stepper/Stepper.scss index 4aeaea04..f25334d0 100644 --- a/src/components/shared/stepper/Stepper.scss +++ b/src/components/shared/stepper/Stepper.scss @@ -7,6 +7,13 @@ div.stepper { background: $bg-color--basic; color: $text-color--secondary; + margin: 0 -10px; + + &.with-config { + padding-left: 5.67%; + padding-right: 5.67%; + } + .MuiSvgIcon-root { font-size: 2rem; opacity: 0.8; @@ -37,3 +44,20 @@ div.stepper { } } } + +h1.active-step { + display: none; +} + +@media (max-width: 500px) { + div.stepper .MuiStepLabel-labelContainer { + display: none; + } + + h1.active-step { + display: block; + text-align: center; + color: $color-black--75; + margin-top: 0; + } +} From 59d7dfe22de3d42a2b1b6d76a593491726bd1b50 Mon Sep 17 00:00:00 2001 From: Igor Poplavsky Date: Wed, 10 Jun 2020 16:44:18 +0300 Subject: [PATCH 4/5] #97: Fixed long document title in dropzone and summary Changed tests --- src/__tests__/integration/upload.test.js | 4 ++-- src/components/layouts/pages/results/summary/Summary.js | 2 +- src/components/layouts/pages/results/summary/Summary.scss | 7 ++++++- .../layouts/pages/upload/dropzone/DropzoneText.js | 2 +- .../layouts/pages/upload/dropzone/DropzoneText.scss | 7 +++++++ 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/__tests__/integration/upload.test.js b/src/__tests__/integration/upload.test.js index b8c541a0..f77c50a4 100644 --- a/src/__tests__/integration/upload.test.js +++ b/src/__tests__/integration/upload.test.js @@ -37,7 +37,7 @@ describe('Upload', () => { integrationTest(async (store, component) => { await storeFile(component, store); - expect(getDropzoneText(component)).toEqual(`${TEST_FILE.name} - ${TEST_FILE.size}`); + expect(getDropzoneText(component)).toEqual(`${TEST_FILE.name}${TEST_FILE.size}`); expect(getNextStepButton(component).props().disabled).toBeFalsy(); }) ); @@ -71,7 +71,7 @@ describe('Upload', () => { moveNext(component); moveBack(component); - expect(getDropzoneText(component)).toEqual(`${TEST_FILE.name} - ${TEST_FILE.size}`); + expect(getDropzoneText(component)).toEqual(`${TEST_FILE.name}${TEST_FILE.size}`); }) ); }); diff --git a/src/components/layouts/pages/results/summary/Summary.js b/src/components/layouts/pages/results/summary/Summary.js index 0e978d5e..555d127e 100644 --- a/src/components/layouts/pages/results/summary/Summary.js +++ b/src/components/layouts/pages/results/summary/Summary.js @@ -23,7 +23,7 @@ function Summary({ fileInfo, resultSummary }) { const compliancePercent = useMemo(() => calculateCompliance(resultSummary), [resultSummary]); return ( -

{fileInfo.name}

+

{fileInfo.name}

{files[0].name} - - {formatFileSize(files[0])} + {formatFileSize(files[0])} ) : ( DROPZONE_TEXT diff --git a/src/components/layouts/pages/upload/dropzone/DropzoneText.scss b/src/components/layouts/pages/upload/dropzone/DropzoneText.scss index ec55d883..583e594c 100644 --- a/src/components/layouts/pages/upload/dropzone/DropzoneText.scss +++ b/src/components/layouts/pages/upload/dropzone/DropzoneText.scss @@ -2,8 +2,15 @@ .dropzone-text { color: $color-black--75; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + display: flex; + flex-direction: column; &__file-size { + text-align: center; color: $color-black--50; font-size: $font-size--basic; } From 865b453cce927e926d8a5985cb91c39c60a3c77f Mon Sep 17 00:00:00 2001 From: Igor Poplavsky Date: Thu, 11 Jun 2020 15:05:00 +0300 Subject: [PATCH 5/5] #25: made pageNavigation component more simple and independence of children Fixed tests; Replaced NavButton with more common LinkButton --- src/__tests__/integration/index.js | 4 +- .../layouts/pages/results/Results.js | 44 ++++++------ .../layouts/pages/settings/Settings.js | 34 +++++----- src/components/layouts/pages/upload/Upload.js | 36 +++++----- .../pages/upload/dropzone/Dropzone.scss | 2 +- .../NavButton.js => linkButton/LinkButton.js} | 15 ++--- .../shared/pageNavigation/PageNavigation.js | 67 +------------------ .../shared/pageNavigation/PageNavigation.scss | 16 +---- 8 files changed, 75 insertions(+), 143 deletions(-) rename src/components/shared/{pageNavigation/NavButton.js => linkButton/LinkButton.js} (60%) diff --git a/src/__tests__/integration/index.js b/src/__tests__/integration/index.js index 4a8a377c..ea24672f 100644 --- a/src/__tests__/integration/index.js +++ b/src/__tests__/integration/index.js @@ -243,8 +243,8 @@ export const navigateWithHeaderLink = (component, linkSelector) => { component.update(); }; -export const getNextStepButton = component => component.find('.page-navigation__end button'); -export const getPrevStepButton = component => component.find('.page-navigation__start button'); +export const getNextStepButton = component => component.find('.page-navigation > .nav-button_forward'); +export const getPrevStepButton = component => component.find('.page-navigation > .nav-button_back'); export const moveBack = component => { getPrevStepButton(component).simulate('click', { button: 0 }); diff --git a/src/components/layouts/pages/results/Results.js b/src/components/layouts/pages/results/Results.js index 58c2ef37..2c2c09fc 100644 --- a/src/components/layouts/pages/results/Results.js +++ b/src/components/layouts/pages/results/Results.js @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useCallback } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { Redirect, useHistory, useParams } from 'react-router-dom'; @@ -7,32 +7,18 @@ import _ from 'lodash'; import AppPages from '../../../AppPages'; import { JOB_STATUS, TASK_STATUS } from '../../../../store/constants'; import { getJobStatus, getTaskStatus } from '../../../../store/job/selectors'; +import { isCompliant } from '../../../../store/job/result/selectors'; import { reset } from '../../../../store/application/actions'; +import Button from '../../../shared/button/Button'; import WizardStep from '../../wizardStep/WizardStep'; import Summary from './summary/Summary'; import PageNavigation from '../../../shared/pageNavigation/PageNavigation'; -import { isCompliant } from '../../../../store/job/result/selectors'; +import LinkButton from '../../../shared/linkButton/LinkButton'; -function Results({ jobStatus, taskStatus, compliant, onBackClick }) { +function Results({ jobStatus, taskStatus, compliant, resetApp }) { const { id: jobId } = useParams(); const history = useHistory(); - - const backButton = useMemo( - () => ({ - label: 'Validate another file', - onClick: () => onBackClick(history), - }), - [history, onBackClick] - ); - - const forwardButton = useMemo( - () => ({ - label: 'Inspect errors', - to: AppPages.INSPECT.url(jobId), - disabled: compliant, - }), - [compliant, jobId] - ); + const onBackClick = useCallback(() => resetApp(history), [history, resetApp]); if (jobStatus === JOB_STATUS.NOT_FOUND) { return ; @@ -44,7 +30,19 @@ function Results({ jobStatus, taskStatus, compliant, onBackClick }) { return ( - + + + + Inspect errors + + ); } @@ -53,7 +51,7 @@ Results.propTypes = { jobStatus: PropTypes.oneOf(_.values(JOB_STATUS)), taskStatus: PropTypes.oneOf(_.values(TASK_STATUS)), compliant: PropTypes.bool.isRequired, - onBackClick: PropTypes.func.isRequired, + resetApp: PropTypes.func.isRequired, }; function mapStateToProps(state) { @@ -66,7 +64,7 @@ function mapStateToProps(state) { function mapDispatchToProps(dispatch) { return { - onBackClick: history => dispatch(reset(history)), + resetApp: history => dispatch(reset(history)), }; } diff --git a/src/components/layouts/pages/settings/Settings.js b/src/components/layouts/pages/settings/Settings.js index 3b9199c2..6cee922c 100644 --- a/src/components/layouts/pages/settings/Settings.js +++ b/src/components/layouts/pages/settings/Settings.js @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useCallback } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { Redirect } from 'react-router-dom'; @@ -6,6 +6,8 @@ import { Redirect } from 'react-router-dom'; import AppPages from '../../../AppPages'; import WizardStep from '../../wizardStep/WizardStep'; import ProfileSelect from './profile/ProfileSelect'; +import Button from '../../../shared/button/Button'; +import LinkButton from '../../../shared/linkButton/LinkButton'; import PageNavigation from '../../../shared/pageNavigation/PageNavigation'; import { getServerGeneralStatus } from '../../../../store/serverInfo/selectors'; import { getJobId } from '../../../../store/job/selectors'; @@ -13,22 +15,9 @@ import { validate } from '../../../../store/job/actions'; import './Settings.scss'; -const backButton = { - label: 'Upload files', - to: AppPages.UPLOAD, -}; - function Settings(props) { const { allServicesAvailable, jobId, onValidateClick } = props; - - const forwardButton = useMemo( - () => ({ - label: 'Validate', - disabled: !allServicesAvailable, - onClick: () => onValidateClick(), - }), - [allServicesAvailable, onValidateClick] - ); + const onForwardClick = useCallback(() => onValidateClick(), [onValidateClick]); if (jobId) { // Once job is initialized and we know its ID redirect to status page to track its progress @@ -42,7 +31,20 @@ function Settings(props) {
- + + + Upload files + + + ); } diff --git a/src/components/layouts/pages/upload/Upload.js b/src/components/layouts/pages/upload/Upload.js index 75b047a7..5c5176f6 100644 --- a/src/components/layouts/pages/upload/Upload.js +++ b/src/components/layouts/pages/upload/Upload.js @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { Redirect } from 'react-router-dom'; +import { Redirect, useHistory } from 'react-router-dom'; import AppPages from '../../../AppPages'; import { validate } from '../../../../store/job/actions'; @@ -12,23 +12,18 @@ import Dropzone from './dropzone/Dropzone'; import WizardStep from '../../wizardStep/WizardStep'; import PageNavigation from '../../../shared/pageNavigation/PageNavigation'; import SettingsCheckbox from './settingsCheckbox/SettingsCheckbox'; +import Button from '../../../shared/button/Button'; function Upload({ filesAttached, isUseSettings, jobId, onValidateClick }) { - const forwardButton = useMemo(() => { - const button = { disabled: !filesAttached }; + const history = useHistory(); + const forwardLabel = useMemo(() => (isUseSettings ? 'Configure' : 'Validate'), [isUseSettings]); + const onForwardClick = useMemo(() => { if (isUseSettings) { - return { - ...button, - label: 'Configure job', - to: AppPages.SETTINGS, - }; + return () => history.push(AppPages.SETTINGS); } - return { - ...button, - label: 'Validate', - onClick: onValidateClick, - }; - }, [filesAttached, onValidateClick, isUseSettings]); + + return onValidateClick; + }, [history, isUseSettings, onValidateClick]); if (!isUseSettings && jobId) { // Once job is initialized and we know its ID redirect to status page to track its progress @@ -38,7 +33,18 @@ function Upload({ filesAttached, isUseSettings, jobId, onValidateClick }) { return ( - } forward={forwardButton} /> + + + + ); } diff --git a/src/components/layouts/pages/upload/dropzone/Dropzone.scss b/src/components/layouts/pages/upload/dropzone/Dropzone.scss index 0ba71fbe..2af8ce19 100644 --- a/src/components/layouts/pages/upload/dropzone/Dropzone.scss +++ b/src/components/layouts/pages/upload/dropzone/Dropzone.scss @@ -10,7 +10,7 @@ flex-direction: column; &__container { - width: 70%; + width: 80%; height: 100px; margin: auto; padding: 20px; diff --git a/src/components/shared/pageNavigation/NavButton.js b/src/components/shared/linkButton/LinkButton.js similarity index 60% rename from src/components/shared/pageNavigation/NavButton.js rename to src/components/shared/linkButton/LinkButton.js index e9d90c14..10142652 100644 --- a/src/components/shared/pageNavigation/NavButton.js +++ b/src/components/shared/linkButton/LinkButton.js @@ -2,16 +2,14 @@ import React from 'react'; import { Link } from 'react-router-dom'; import PropTypes from 'prop-types'; import classNames from 'classnames'; + import Button from '../button/Button'; -function NavButton(props) { - const { to, disabled, type, variant } = props; +function LinkButton(props) { + const { to, disabled, variant, className } = props; return ( - + @@ -19,11 +17,10 @@ function NavButton(props) { ); } -NavButton.propTypes = { +LinkButton.propTypes = { to: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.func]).isRequired, - type: PropTypes.string.isRequired, variant: PropTypes.string, disabled: PropTypes.bool, }; -export default NavButton; +export default LinkButton; diff --git a/src/components/shared/pageNavigation/PageNavigation.js b/src/components/shared/pageNavigation/PageNavigation.js index d65b5e57..43c5d6a2 100644 --- a/src/components/shared/pageNavigation/PageNavigation.js +++ b/src/components/shared/pageNavigation/PageNavigation.js @@ -1,70 +1,9 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import NavButton from './NavButton'; -import Button from '../button/Button'; -import './PageNavigation.scss'; - -const TYPE = { - BACK: 'back', - CENTER: 'center', - FORWARD: 'forward', -}; -const VARIANTS = { - [TYPE.BACK]: 'outlined', - [TYPE.CENTER]: 'contained', - [TYPE.FORWARD]: 'contained', -}; - -function PageNavigation(props) { - const { back, forward, center } = props; - return ( - - ); -} - -function getComponent(componentObject, type) { - if (React.isValidElement(componentObject)) { - return componentObject; - } - if (componentObject?.to) { - return ( - - {componentObject.label} - - ); - } - - if (componentObject?.onClick) { - return ( - - ); - } +import './PageNavigation.scss'; - return null; +function PageNavigation({ children }) { + return ; } -const ButtonInterface = PropTypes.shape({ - to: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.func]), - label: PropTypes.string.isRequired, - disabled: PropTypes.bool, - onClick: PropTypes.func, -}); - -PageNavigation.propTypes = { - back: PropTypes.oneOfType([ButtonInterface, PropTypes.element]), - forward: PropTypes.oneOfType([ButtonInterface, PropTypes.element]), -}; - export default PageNavigation; diff --git a/src/components/shared/pageNavigation/PageNavigation.scss b/src/components/shared/pageNavigation/PageNavigation.scss index 3472cb42..033c3d3c 100644 --- a/src/components/shared/pageNavigation/PageNavigation.scss +++ b/src/components/shared/pageNavigation/PageNavigation.scss @@ -1,17 +1,7 @@ .page-navigation { display: flex; padding: 20px 0 10px 0; - - section { - display: flex; - flex-grow: 1; - flex-basis: 0; - justify-content: center; - align-items: start; - - .app-link { - color: inherit; - display: flex; - } - } + justify-content: space-between; + width: 80%; + margin: auto; }