From 11489afabd2afa34dd49350dd3813c418a2a7a99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Mar 2024 13:54:29 -0500 Subject: [PATCH 01/11] Bump react-router-dom from 6.22.0 to 6.22.2 (#2300) Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.22.0 to 6.22.2. - [Release notes](https://github.com/remix-run/react-router/releases) - [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md) - [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.22.2/packages/react-router-dom) --- updated-dependencies: - dependency-name: react-router-dom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/yarn.lock b/yarn.lock index ea85d2610c..265049d6aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9977,10 +9977,10 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== -"@remix-run/router@1.15.0": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.15.0.tgz#461a952c2872dd82c8b2e9b74c4dfaff569123e2" - integrity sha512-HOil5aFtme37dVQTB6M34G95kPM3MMuqSmIRVCC52eKV+Y/tGSqw9P3rWhlAx6A+mz+MoX+XxsGsNJbaI5qCgQ== +"@remix-run/router@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.15.2.tgz#35726510d332ba5349c6398d13259d5da184553d" + integrity sha512-+Rnav+CaoTE5QJc4Jcwh5toUpnVLKYbpU6Ys0zqbakqbaLQHeglLVHPfxOiQqdNmUy5C2lXz5dwC6tQNX2JW2Q== "@repeaterjs/repeater@^3.0.4": version "3.0.5" @@ -13013,9 +13013,9 @@ integrity sha512-Abq9fBviLV93OiXMu+f6r0elxCzRwc0RC5f99cU892uBITL44pTvgvEqlRlPRi8EGcO1z7Cp8A4d0s/p3J/+Nw== "@types/node@^16.18.39": - version "16.18.85" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.85.tgz#17b5338c958efd67b064b92fbef41ad0333c397b" - integrity sha512-un7Bj6CPCRLxG2qp+9enNVFuRWCDKKOS6Q/FSpJ4xyrpLNJnRdAQERM2sJ6esaGvl02nK6kiGcMTb0pqknm62g== + version "16.18.86" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.86.tgz#87426ce3747a4c135229e15765cbf9c5a18d280c" + integrity sha512-QMvdZf+ZTSiv7gspwhqbfB7Y5DmbYgCsUnakS8Ul9uRbJQehDKaM7SL+GbcDS003Lh7VK4YlelHsRm9HCv26eA== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -28300,19 +28300,19 @@ react-refresh@^0.11.0: integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== react-router-dom@^6.5.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.22.0.tgz#177c8bd27146decbb991eafb5df159f7a9f70035" - integrity sha512-z2w+M4tH5wlcLmH3BMMOMdrtrJ9T3oJJNsAlBJbwk+8Syxd5WFJ7J5dxMEW0/GEXD1BBis4uXRrNIz3mORr0ag== + version "6.22.2" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.22.2.tgz#8233968a8a576f3006e5549c80f3527d2598fc9c" + integrity sha512-WgqxD2qySEIBPZ3w0sHH+PUAiamDeszls9tzqMPBDA1YYVucTBXLU7+gtRfcSnhe92A3glPnvSxK2dhNoAVOIQ== dependencies: - "@remix-run/router" "1.15.0" - react-router "6.22.0" + "@remix-run/router" "1.15.2" + react-router "6.22.2" -react-router@6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.22.0.tgz#a22b44851a79dafc6b944cb418db3e80622b9be1" - integrity sha512-q2yemJeg6gw/YixRlRnVx6IRJWZD6fonnfZhN1JIOhV2iJCPeRNSH3V1ISwHf+JWcESzLC3BOLD1T07tmO5dmg== +react-router@6.22.2: + version "6.22.2" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.22.2.tgz#27e77e4c635a5697693b922d131d773451c98a5b" + integrity sha512-YD3Dzprzpcq+tBMHBS822tCjnWD3iIZbTeSXMY9LPSG541EfoBGyZ3bS25KEnaZjLcmQpw2AVLkFyfgXY8uvcw== dependencies: - "@remix-run/router" "1.15.0" + "@remix-run/router" "1.15.2" react-scripts@5.0.1: version "5.0.1" From d2a003d87f37dff5d942019213cf46cc874e111f Mon Sep 17 00:00:00 2001 From: haworku Date: Wed, 6 Mar 2024 13:49:30 -0800 Subject: [PATCH 02/11] Add V2 UnlockSubmitModal (#2301) * Add v2 of unlock submit modal * Cleanup --- .../Modal/V2/UnlockSubmitModalV2.tsx | 322 ++++++++++++++++++ .../src/testHelpers/jestRateHelpers.tsx | 17 +- 2 files changed, 326 insertions(+), 13 deletions(-) create mode 100644 services/app-web/src/components/Modal/V2/UnlockSubmitModalV2.tsx diff --git a/services/app-web/src/components/Modal/V2/UnlockSubmitModalV2.tsx b/services/app-web/src/components/Modal/V2/UnlockSubmitModalV2.tsx new file mode 100644 index 0000000000..794f8d56fc --- /dev/null +++ b/services/app-web/src/components/Modal/V2/UnlockSubmitModalV2.tsx @@ -0,0 +1,322 @@ +import { UnlockedHealthPlanFormDataType } from '../../../common-code/healthPlanFormDataType' +import React, { useEffect, useState } from 'react' +import { FormGroup, ModalRef, Textarea } from '@trussworks/react-uswds' +import { useNavigate } from 'react-router-dom' +import { + HealthPlanPackage, + useSubmitHealthPlanPackageMutation, + useUnlockHealthPlanPackageMutation, + Rate, +} from '../../../gen/gqlClient' +import { + submitMutationWrapper, + unlockMutationWrapper, +} from '../../../gqlHelpers' +import { useFormik } from 'formik' +import { usePrevious } from '../../../hooks/usePrevious' +import { Modal } from '../Modal' +import { PoliteErrorMessage } from '../../PoliteErrorMessage' +import * as Yup from 'yup' +import styles from './UnlockSubmitModal.module.scss' +import { GenericApiErrorProps } from '../../Banner/GenericApiErrorBanner/GenericApiErrorBanner' +import { ERROR_MESSAGES } from '../../../constants/errors' + +const PACKAGE_UNLOCK_SUBMIT_TYPES = [ + 'SUBMIT_PACKAGE', + 'RESUBMIT_PACKAGE', + 'UNLOCK_PACKAGE', +] as const +const RATE_UNLOCK_SUBMIT_TYPES = [ + 'SUBMIT_RATE', + 'RESUBMIT_RATE', + 'UNLOCK_RATE', +] as const +type PackageModalType = (typeof PACKAGE_UNLOCK_SUBMIT_TYPES)[number] +type RateModalType = (typeof RATE_UNLOCK_SUBMIT_TYPES)[number] +type SharedModalType = PackageModalType & RateModalType +type SharedAdditionalProps = { + submissionName?: string + modalRef: React.RefObject + setIsSubmitting?: React.Dispatch> +} + +type RateModalProps = { + submissionData: Rate + modalType: RateModalType[number] +} & SharedAdditionalProps + +type PackageModalProps = { + submissionData: UnlockedHealthPlanFormDataType | HealthPlanPackage + modalType: PackageModalType +} & SharedAdditionalProps + +type UnlockSubmitModalProps = PackageModalProps | RateModalProps + +type ModalValueType = { + modalHeading?: string + onSubmitText?: string + modalDescription?: string + inputHint?: string + unlockSubmitModalInputValidation?: string + errorHeading: string + errorSuggestion?: string +} + +const modalValueDictionary: { [Property in SharedModalType]: ModalValueType } = + { + RESUBMIT_PACKAGE: { + modalHeading: 'Summarize changes', + onSubmitText: 'Resubmit', + modalDescription: + 'Once you submit, this package will be sent to CMS for review and you will no longer be able to make changes.', + inputHint: 'Provide summary of all changes made to this submission', + unlockSubmitModalInputValidation: + 'You must provide a summary of changes', + errorHeading: ERROR_MESSAGES.resubmit_error_heading, + }, + UNLOCK_PACKAGE: { + modalHeading: 'Reason for unlocking submission', + onSubmitText: 'Unlock', + inputHint: 'Provide reason for unlocking', + unlockSubmitModalInputValidation: + 'You must provide a reason for unlocking this submission', + errorHeading: ERROR_MESSAGES.unlock_error_heading, + }, + SUBMIT_PACKAGE: { + modalHeading: 'Ready to submit?', + onSubmitText: 'Submit', + modalDescription: + 'Submitting this package will send it to CMS to begin their review.', + errorHeading: ERROR_MESSAGES.submit_error_heading, + errorSuggestion: ERROR_MESSAGES.submit_error_suggestion, + }, + RESUBMIT_RATE: { + modalHeading: 'Summarize changes', + onSubmitText: 'Resubmit', + modalDescription: + 'Once you submit, this rate will be sent to CMS for review and you will no longer be able to make changes.', + inputHint: 'Provide summary of all changes made to this rate', + unlockSubmitModalInputValidation: + 'You must provide a summary of changes', + errorHeading: ERROR_MESSAGES.resubmit_error_heading, + }, + UNLOCK_RATE: { + modalHeading: 'Reason for unlocking rate', + onSubmitText: 'Unlock', + inputHint: 'Provide reason for unlocking', + unlockSubmitModalInputValidation: + 'You must provide a reason for unlocking this rate', + errorHeading: ERROR_MESSAGES.unlock_error_heading, + }, + SUBMIT_RATE: { + modalHeading: 'Ready to submit?', + onSubmitText: 'Submit', + modalDescription: + 'Submitting this rate will send it to CMS to begin their review.', + errorHeading: ERROR_MESSAGES.submit_error_heading, + errorSuggestion: ERROR_MESSAGES.submit_error_suggestion, + }, + } + +export const UnlockSubmitModalV2 = ({ + submissionData, + submissionName, + modalType, + modalRef, + setIsSubmitting, +}: UnlockSubmitModalProps): React.ReactElement | null => { + const [focusErrorsInModal, setFocusErrorsInModal] = useState(true) + const [modalAlert, setModalAlert] = useState< + GenericApiErrorProps | undefined + >(undefined) + const navigate = useNavigate() + + const modalValues: ModalValueType = + modalValueDictionary[modalType as SharedModalType] + + const modalFormInitialValues = { + unlockSubmitModalInput: '', + } + + const [submitHealthPlanPackage, { loading: submitMutationLoading }] = + useSubmitHealthPlanPackageMutation() // TODO this should be submitContract - linked rates epic + const [unlockHealthPlanPackage, { loading: unlockMutationLoading }] = + useUnlockHealthPlanPackageMutation() // TODO this should be unlockContract - linked rates epic + + // TODO submitRate and unlockRate should also be set up here - nunlock and edit rate epic + const formik = useFormik({ + initialValues: modalFormInitialValues, + validationSchema: Yup.object().shape({ + unlockSubmitModalInput: Yup.string().defined( + modalValues.unlockSubmitModalInputValidation + ), + }), + onSubmit: (values) => onSubmit(values.unlockSubmitModalInput), + }) + + const mutationLoading = + modalType === 'UNLOCK_PACKAGE' + ? unlockMutationLoading + : submitMutationLoading + const isSubmitting = mutationLoading || formik.isSubmitting + const includesFormInput = + modalType === 'UNLOCK_PACKAGE' || + modalType === 'RESUBMIT_PACKAGE' || + modalType === 'UNLOCK_RATE' || + modalType === 'RESUBMIT_RATE' + + const prevSubmitting = usePrevious(isSubmitting) + + const submitHandler = async () => { + setFocusErrorsInModal(true) + if (includesFormInput) { + formik.handleSubmit() + } else { + await onSubmit() + } + } + + const onSubmit = async (unlockSubmitModalInput?: string): Promise => { + let result + + switch (modalType) { + case 'UNLOCK_PACKAGE': + if (unlockSubmitModalInput) { + await unlockMutationWrapper( + unlockHealthPlanPackage, + submissionData.id, + unlockSubmitModalInput + ) + } + break + + case 'SUBMIT_PACKAGE' || 'RESUBMIT_PACKAGE': + result = await submitMutationWrapper( + submitHealthPlanPackage, + submissionData.id, + unlockSubmitModalInput + ) + break + + case 'UNLOCK_RATE': + console.info('unlock rate not implemented yet') + break + + case 'SUBMIT_RATE' || 'RESUBMIT_RATE': + console.info('submit/resubmit rate not implemented yet') + break + } + + //Allow submitting/unlocking to continue on EMAIL_ERROR. + if (result instanceof Error && result.cause === 'EMAIL_ERROR') { + modalRef.current?.toggleModal(undefined, false) + + if ( + (modalType === 'SUBMIT_PACKAGE' || + modalType === 'RESUBMIT_PACKAGE') && + submissionName + ) { + // TODO make sure dashboard data is up to date + navigate( + `/dashboard/submissions?justSubmitted=${submissionName}` + ) + } else if (modalType === 'UNLOCK_PACKAGE') { + // TODO make sure on unlock the submission banners are up to date + } + } else if (result instanceof Error) { + setModalAlert({ + heading: modalValues.errorHeading, + message: result.message, + // When we have generic/unknown errors override any suggestions and display the fallback "please refresh text" + suggestion: + result.message === ERROR_MESSAGES.submit_error_generic || + result.message === ERROR_MESSAGES.unlock_error_generic + ? undefined + : modalValues.errorSuggestion, + }) + } else { + modalRef.current?.toggleModal(undefined, false) + if ( + (modalType === 'SUBMIT_PACKAGE' || + modalType === 'RESUBMIT_PACKAGE') && + submissionName + ) { + navigate( + `/dashboard/submissions?justSubmitted=${submissionName}` + ) + } + } + } + + // Focus submittedReason field in submission modal on Resubmit click when errors exist + useEffect(() => { + if (focusErrorsInModal && formik.errors.unlockSubmitModalInput) { + const fieldElement: HTMLElement | null = document.querySelector( + `[name="unlockSubmitModalInput"]` + ) + if (fieldElement) { + fieldElement.focus() + setFocusErrorsInModal(false) + } else { + console.info('Attempting to focus element that does not exist') + } + } + }, [focusErrorsInModal, formik.errors]) + + useEffect(() => { + if ( + prevSubmitting !== isSubmitting && + prevSubmitting !== undefined && + setIsSubmitting + ) { + setIsSubmitting(isSubmitting) + } + }, [isSubmitting, setIsSubmitting, prevSubmitting]) + + return ( + + {includesFormInput ? ( +
+ {modalValues.modalDescription && ( +

{modalValues.modalDescription}

+ )} + + {formik.errors.unlockSubmitModalInput && ( + + {formik.errors.unlockSubmitModalInput} + + )} + {modalValues.inputHint && ( + + {modalValues.inputHint} + + )} +