diff --git a/src/applications/discharge-wizard/components/v2/ResultsPage.jsx b/src/applications/discharge-wizard/components/v2/ResultsPage.jsx new file mode 100644 index 000000000000..5e7a3389150e --- /dev/null +++ b/src/applications/discharge-wizard/components/v2/ResultsPage.jsx @@ -0,0 +1,52 @@ +import React, { useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; + +import { pageSetup } from '../../utilities/page-setup'; +import { ROUTES } from '../../constants'; + +import ResultsSummary from './resultsComponents/ResultsSummary'; + +const ResultsPage = ({ formResponses, router, viewedIntroPage }) => { + const H1 = 'Your Steps for Upgrading Your Discharge'; + + useEffect( + () => { + pageSetup(H1); + }, + [H1], + ); + + useEffect( + () => { + if (!viewedIntroPage) { + router.push(ROUTES.HOME); + } + }, + [router, viewedIntroPage], + ); + + return ( +
+

{H1}

+
+ +
+
+ ); +}; + +ResultsPage.propTypes = { + formResponses: PropTypes.object.isRequired, + router: PropTypes.shape({ + push: PropTypes.func, + }).isRequired, + viewedIntroPage: PropTypes.bool.isRequired, +}; + +const mapStateToProps = state => ({ + formResponses: state?.dischargeUpgradeWizard?.duwForm?.form, + viewedIntroPage: state?.dischargeUpgradeWizard?.duwForm?.viewedIntroPage, +}); + +export default connect(mapStateToProps)(ResultsPage); diff --git a/src/applications/discharge-wizard/components/v2/ReviewPage.jsx b/src/applications/discharge-wizard/components/v2/ReviewPage.jsx index 697e0f951f07..823d7f6fead0 100644 --- a/src/applications/discharge-wizard/components/v2/ReviewPage.jsx +++ b/src/applications/discharge-wizard/components/v2/ReviewPage.jsx @@ -28,6 +28,10 @@ const ReviewPage = ({ formResponses, router, viewedIntroPage }) => { [router, viewedIntroPage], ); + const onEditAnswerClick = route => { + router.push(route); + }; + const renderReviewAnswers = () => { return Object.keys(SHORT_NAME_MAP).map(shortName => { if (formResponses[shortName] === null) { @@ -43,15 +47,12 @@ const ReviewPage = ({ formResponses, router, viewedIntroPage }) => { className="vads-u-margin-bottom--0 vads-u-padding-y--3 vads-u-padding-x--1p5 answer-review" > {reviewLabel} - {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} - onEditAnswerClick(ROUTES[shortName])} name={shortName} - > - Edit - + text="Edit" + /> ) ); diff --git a/src/applications/discharge-wizard/components/v2/resultsComponents/ResultsSummary.jsx b/src/applications/discharge-wizard/components/v2/resultsComponents/ResultsSummary.jsx new file mode 100644 index 000000000000..0f0bebdeddb0 --- /dev/null +++ b/src/applications/discharge-wizard/components/v2/resultsComponents/ResultsSummary.jsx @@ -0,0 +1,49 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + determineBoardObj, + determineAirForceAFRBAPortal, + determineFormData, +} from '../../../helpers'; + +import { + SHORT_NAME_MAP, + RESPONSES, +} from '../../../constants/question-data-map'; + +const ResultsSummary = ({ formResponses }) => { + const forReconsideration = + [ + RESPONSES.PREV_APPLICATION_TYPE_3A, + RESPONSES.PREV_APPLICATION_TYPE_3B, + ].includes(formResponses[SHORT_NAME_MAP.PREV_APPLICATION_TYPE]) && + ![ + RESPONSES.FAILURE_TO_EXHAUST_1A, + RESPONSES.FAILURE_TO_EXHAUST_1B, + ].includes(formResponses[SHORT_NAME_MAP.FAILURE_TO_EXHAUST]); + + const airForceAFRBAPortal = determineAirForceAFRBAPortal(formResponses); + const formNumber = determineFormData(formResponses).num; + const dischargeBoard = determineBoardObj(formResponses).name; + const serviceBranch = formResponses[SHORT_NAME_MAP.SERVICE_BRANCH]; + const isReconsideration = forReconsideration ? ' for reconsideration' : ''; + + let summary = `Based on your answers, you need to complete Department of Defense (DoD) Form ${formNumber} and send it to the ${dischargeBoard} for the ${serviceBranch} ${isReconsideration}.`; + + if (airForceAFRBAPortal) { + summary = + 'Based on your answers, you need to complete an Application for Correction of Military Record (DD 149). You can download this form from the Air Force Review Boards Agency Website and Portal.'; + } + + return ( +
+

{summary}

+
+ ); +}; + +ResultsSummary.propTypes = { + formResponses: PropTypes.object.isRequired, +}; + +export default ResultsSummary; diff --git a/src/applications/discharge-wizard/helpers/index.jsx b/src/applications/discharge-wizard/helpers/index.jsx index 34d2fbbd59b5..43ed7f1a1acf 100644 --- a/src/applications/discharge-wizard/helpers/index.jsx +++ b/src/applications/discharge-wizard/helpers/index.jsx @@ -1,5 +1,6 @@ import React from 'react'; import moment from 'moment'; +import { differenceInYears } from 'date-fns'; import * as options from 'platform/static-data/options-for-select'; import { questionLabels, prevApplicationYearCutoff } from '../constants'; import { SHORT_NAME_MAP, RESPONSES } from '../constants/question-data-map'; @@ -62,7 +63,7 @@ export const board = (formValues, noDRB) => { if (formValues['1_branchOfService'] === 'airForce') { return { - name: 'Air Force Review Boards Agency (AFDRB) for the Air Force', + name: 'Air Force Review Boards Agency (AFDRB)', abbr: 'AFDRB', }; } @@ -260,13 +261,15 @@ export const deriveIsAirForceAFRBAPortal = formValues => board(formValues).abbr === 'BCMR' && formData(formValues).num === 149; +// v2 Helpers + export const answerReviewLabel = (key, formValues) => { const answer = formValues[key]; const monthObj = options.months.find( m => String(m.value) === formValues[SHORT_NAME_MAP.DISCHARGE_MONTH], ); - const dischargeMonth = monthObj && monthObj.label; + const dischargeMonth = (monthObj && monthObj.label) || ''; switch (key) { case SHORT_NAME_MAP.SERVICE_BRANCH: @@ -279,7 +282,7 @@ export const answerReviewLabel = (key, formValues) => { return 'I was discharged before 1992.'; } - return `I was discharged in ${dischargeMonth || ''} ${formValues[key]}.`; + return `I was discharged in ${dischargeMonth} ${formValues[key]}.`; case SHORT_NAME_MAP.PREV_APPLICATION: if (answer === RESPONSES.PREV_APPLICATION_1) { return 'I have previously applied for a discharge upgrade for this period of service.'; @@ -308,3 +311,99 @@ export const answerReviewLabel = (key, formValues) => { } } }; + +export const determineBoardObj = (formResponses, noDRB) => { + if (!formResponses) { + return null; + } + + const prevAppType = [ + RESPONSES.PREV_APPLICATION_TYPE_1, + RESPONSES.PREV_APPLICATION_TYPE_4, + ].includes(formResponses[SHORT_NAME_MAP.PREV_APPLICATION_TYPE]); + + const noPrevApp = + formResponses[SHORT_NAME_MAP.PREV_APPLICATION] === + RESPONSES.PREV_APPLICATION_2; + + const preAppDateBefore = [ + RESPONSES.PREV_APPLICATION_YEAR_1A, + RESPONSES.PREV_APPLICATION_YEAR_1B, + RESPONSES.PREV_APPLICATION_YEAR_1C, + ].includes(formResponses[SHORT_NAME_MAP.PREV_APPLICATION_YEAR]); + + const courtMartial = + formResponses[SHORT_NAME_MAP.COURT_MARTIAL] === RESPONSES.COURT_MARTIAL_1; + + const transgender = + formResponses[SHORT_NAME_MAP.REASON] === RESPONSES.REASON_5; + const intention = + formResponses[SHORT_NAME_MAP.INTENTION] === RESPONSES.INTENTION_1; + const dischargeYear = formResponses[SHORT_NAME_MAP.DISCHARGE_YEAR]; + const dischargeMonth = formResponses[SHORT_NAME_MAP.DISCHARGE_MONTH] || 0; + + const oldDischarge = + differenceInYears(new Date(), new Date(dischargeMonth, dischargeYear)) >= + 15; + + const failureToExhaust = [ + RESPONSES.FAILURE_TO_EXHAUST_1A, + RESPONSES.FAILURE_TO_EXHAUST_1B, + ].includes(formResponses[SHORT_NAME_MAP.FAILURE_TO_EXHAUST]); + + let boardObj = { + name: 'Board for Correction of Naval Records (BCNR)', + abbr: 'BCNR', + }; + if ( + [RESPONSES.ARMY, RESPONSES.AIR_FORCE, RESPONSES.COAST_GUARD].includes( + formResponses[SHORT_NAME_MAP.SERVICE_BRANCH], + ) + ) { + boardObj = { + name: 'Board for Correction of Military Records (BCMR)', + abbr: 'BCMR', + }; + } + + if (noDRB) { + return boardObj; + } + + if (noPrevApp || preAppDateBefore || prevAppType || failureToExhaust) { + if (courtMartial || transgender || intention || oldDischarge) { + return boardObj; + } + + if (formResponses[SHORT_NAME_MAP.SERVICE_BRANCH] === RESPONSES.AIR_FORCE) { + return { + name: 'Air Force Review Boards Agency (AFDRB)', + abbr: 'AFDRB', + }; + } + + return { name: 'Discharge Review Board (DRB)', abbr: 'DRB' }; + } + + return boardObj; +}; + +export const determineFormData = formResponses => { + const boardData = determineBoardObj(formResponses); + if (boardData?.abbr === 'DRB') { + return { + num: 293, + link: + 'http://www.esd.whs.mil/Portals/54/Documents/DD/forms/dd/dd0293.pdf', + }; + } + return { + num: 149, + link: 'https://www.esd.whs.mil/Portals/54/Documents/DD/forms/dd/dd0149.pdf', + }; +}; + +export const determineAirForceAFRBAPortal = formResponses => + formResponses[SHORT_NAME_MAP.SERVICE_BRANCH] === RESPONSES.AIR_FORCE && + determineBoardObj(formResponses).abbr === 'BCMR' && + determineFormData(formResponses).num === 149; diff --git a/src/applications/discharge-wizard/routes.jsx b/src/applications/discharge-wizard/routes.jsx index 61a1cc26739b..4a75f0ce8f63 100644 --- a/src/applications/discharge-wizard/routes.jsx +++ b/src/applications/discharge-wizard/routes.jsx @@ -20,6 +20,7 @@ import PrevApplicationYear from './components/v2/questions/PrevApplicationYear'; import PriorService from './components/v2/questions/PriorService'; import FailureToExhaust from './components/v2/questions/FailureToExhaust'; import ReviewPage from './components/v2/ReviewPage'; +import ResultsPage from './components/v2/ResultsPage'; const envChildRoutes = environment.isProduction() ? [ @@ -47,6 +48,7 @@ const envChildRoutes = environment.isProduction() { path: 'prior-service', component: PriorService }, { path: 'failure-to-exhaust', component: FailureToExhaust }, { path: 'review', component: ReviewPage }, + { path: 'results', component: ResultsPage }, ]; const routes = { path: '/', diff --git a/src/applications/discharge-wizard/tests/v2/cypress/discharge-upgrade-wizard.cypress.spec.js b/src/applications/discharge-wizard/tests/v2/cypress/discharge-upgrade-wizard.cypress.spec.js index d1ee1435019c..a6e2b21267dd 100644 --- a/src/applications/discharge-wizard/tests/v2/cypress/discharge-upgrade-wizard.cypress.spec.js +++ b/src/applications/discharge-wizard/tests/v2/cypress/discharge-upgrade-wizard.cypress.spec.js @@ -141,8 +141,12 @@ xdescribe('Discharge Upgrade Wizard', () => { h.selectRadio(h.FAILURE_TO_EXHAUST_INPUT, 1); h.clickBack(); - // // REVIEW - // h.verifyUrl(ROUTES.REVIEW); + // REVIEW + h.verifyUrl(ROUTES.REVIEW); + + // FAILURE_TO_EXHAUST + h.verifyUrl(ROUTES.FAILURE_TO_EXHAUST); + h.clickBack(); // PREVIOUS_APPLICATION_TYPE h.verifyUrl(ROUTES.PREV_APPLICATION_TYPE); diff --git a/src/applications/discharge-wizard/tests/v2/helpers/index.unit.spec.js b/src/applications/discharge-wizard/tests/v2/helpers/index.unit.spec.js new file mode 100644 index 000000000000..0b464d798238 --- /dev/null +++ b/src/applications/discharge-wizard/tests/v2/helpers/index.unit.spec.js @@ -0,0 +1,99 @@ +import { expect } from 'chai'; + +import { + answerReviewLabel, + determineBoardObj, + determineAirForceAFRBAPortal, + determineFormData, +} from '../../../helpers/index'; +import { + RESPONSES, + SHORT_NAME_MAP, +} from '../../../constants/question-data-map'; + +describe('Discharge Wizard helpers', () => { + const formResponses = { + SERVICE_BRANCH: RESPONSES.ARMY, + DISCHARGE_YEAR: '2022', + DISCHARGE_MONTH: '', + REASON: RESPONSES.REASON_1, + DISCHARGE_TYPE: null, + INTENTION: RESPONSES.INTENTION_1, + COURT_MARTIAL: RESPONSES.COURT_MARTIAL_1, + PREV_APPLICATION: RESPONSES.PREV_APPLICATION_1, + PREV_APPLICATION_YEAR: RESPONSES.PREV_APPLICATION_YEAR_1A, + PREV_APPLICATION_TYPE: RESPONSES.PREV_APPLICATION_TYPE_1, + FAILURE_TO_EXHAUST: null, + PRIOR_SERVICE: RESPONSES.PRIOR_SERVICE_1, + }; + + it('helps determine which form number and portal to use', () => { + const formNumber = determineFormData(formResponses); + expect(formNumber).to.deep.equal({ + num: 149, + link: + 'https://www.esd.whs.mil/Portals/54/Documents/DD/forms/dd/dd0149.pdf', + }); + }); + + it('helps determine which board for the user to seek their request for re-review', () => { + const boardOfReview = determineBoardObj(formResponses); + expect(boardOfReview).to.deep.equal({ + name: 'Board for Correction of Military Records (BCMR)', + abbr: 'BCMR', + }); + }); + + it('determines if the new air force portal shows', () => { + const isAirForcePortal = determineAirForceAFRBAPortal({ + SERVICE_BRANCH: RESPONSES.AIR_FORCE, + DISCHARGE_YEAR: '2020', + DISCHARGE_MONTH: '', + REASON: RESPONSES.REASON_4, + DISCHARGE_TYPE: null, + INTENTION: RESPONSES.INTENTION_2, + COURT_MARTIAL: RESPONSES.COURT_MARTIAL_1, + PREV_APPLICATION: RESPONSES.PREV_APPLICATION_1, + PREV_APPLICATION_YEAR: null, + PREV_APPLICATION_TYPE: null, + FAILURE_TO_EXHAUST: null, + PRIOR_SERVICE: RESPONSES.PRIOR_SERVICE_3, + }); + expect(isAirForcePortal).to.equal(true); + }); + + it('determines correct answer review label', () => { + const serviceBranchLabel = answerReviewLabel( + SHORT_NAME_MAP.SERVICE_BRANCH, + formResponses, + ); + const dischargeYearLabel = answerReviewLabel( + SHORT_NAME_MAP.DISCHARGE_YEAR, + formResponses, + ); + + const prevApplicationLabel = answerReviewLabel( + SHORT_NAME_MAP.PREV_APPLICATION, + formResponses, + ); + const courtMartialLabel = answerReviewLabel( + SHORT_NAME_MAP.COURT_MARTIAL, + formResponses, + ); + + const prevApplicationTypeLabel = answerReviewLabel( + SHORT_NAME_MAP.PREV_APPLICATION_TYPE, + formResponses, + ); + + expect(serviceBranchLabel).to.equal('I served in the Army.'); + expect(dischargeYearLabel).to.equal('I was discharged in 2022.'); + expect(prevApplicationLabel).to.equal( + 'I have previously applied for a discharge upgrade for this period of service.', + ); + expect(courtMartialLabel).to.equal(RESPONSES.COURT_MARTIAL_1); + expect(prevApplicationTypeLabel).to.equal( + RESPONSES.PREV_APPLICATION_TYPE_1, + ); + }); +}); diff --git a/src/applications/discharge-wizard/tests/v2/PrevApplicationType.unit.spec.js b/src/applications/discharge-wizard/tests/v2/results/results.unit.spec.js similarity index 71% rename from src/applications/discharge-wizard/tests/v2/PrevApplicationType.unit.spec.js rename to src/applications/discharge-wizard/tests/v2/results/results.unit.spec.js index ab7d96c32bd4..44387f157e32 100644 --- a/src/applications/discharge-wizard/tests/v2/PrevApplicationType.unit.spec.js +++ b/src/applications/discharge-wizard/tests/v2/results/results.unit.spec.js @@ -3,9 +3,9 @@ import { Provider } from 'react-redux'; import { render } from '@testing-library/react'; import { expect } from 'chai'; import sinon from 'sinon'; -import { ROUTES } from '../../constants'; +import { ROUTES } from '../../../constants'; -import PrevApplicationType from '../../components/v2/questions/PrevApplicationType'; +import ResultsPage from '../../../components/v2/ResultsPage'; const mockStoreStandard = { getState: () => ({ @@ -37,7 +37,6 @@ const pushStub = sinon.stub(); const propsStandard = { formResponses: {}, - setPrevApplicationType: () => {}, router: { push: pushStub, }, @@ -46,32 +45,31 @@ const propsStandard = { const propsNoIntroPage = { formResponses: {}, - setPrevApplicationType: () => {}, router: { push: pushStub, }, viewedIntroPage: false, }; -describe('Previous Application Type Page', () => { +describe('Results Page', () => { afterEach(() => { pushStub.resetHistory(); }); - it('should correctly load the Previous Application page in the standard flow', () => { + it('should correctly load the Results page in the standard flow', () => { const screen = render( - + , ); - expect(screen.getByTestId('duw-prev_application_type')).to.exist; + expect(screen.getByTestId('duw-results')).to.exist; }); it('should redirect to home when the intro page has not been viewed', () => { render( - + , ); diff --git a/src/applications/discharge-wizard/utilities/page-navigation.js b/src/applications/discharge-wizard/utilities/page-navigation.js index cd13a893e6ea..7c117a0c2c43 100644 --- a/src/applications/discharge-wizard/utilities/page-navigation.js +++ b/src/applications/discharge-wizard/utilities/page-navigation.js @@ -38,8 +38,12 @@ export const navigateForward = (SHORT_NAME, formResponses, router) => { } if (DISPLAY_CONDITIONS?.[nextShortName]) { - // Found entry in DISPLAY_CONDITIONS for next question - if (displayConditionsMet(nextShortName, formResponses)) { + // Found entry in DISPLAY_CONDITIONS for next question. + // Also accounts for editing answers and if there are answers already saved, we continue routing to review page. + if ( + displayConditionsMet(nextShortName, formResponses) && + !formResponses[nextShortName] + ) { pushToRoute(nextShortName, router); return; }