diff --git a/services/app-web/src/constants/routes.ts b/services/app-web/src/constants/routes.ts index 22e98b4ded..747b7b8875 100644 --- a/services/app-web/src/constants/routes.ts +++ b/services/app-web/src/constants/routes.ts @@ -12,6 +12,7 @@ const ROUTES = [ 'HELP', 'SETTINGS', 'RATES_SUMMARY', + 'RATE_EDIT', 'SUBMISSIONS', 'SUBMISSIONS_NEW', 'SUBMISSIONS_TYPE', @@ -46,7 +47,8 @@ const RoutesRecord: Record = { GRAPHQL_EXPLORER: '/dev/graphql-explorer', HELP: '/help', SETTINGS: '/settings', - RATES_SUMMARY: 'rates/:id', + RATES_SUMMARY: '/rates/:id', + RATE_EDIT: '/rates/:id/edit', SUBMISSIONS: '/submissions', SUBMISSIONS_NEW: '/submissions/new', SUBMISSIONS_EDIT_TOP_LEVEL: '/submissions/:id/edit/*', @@ -122,6 +124,7 @@ const PageTitlesRecord: Record = { DASHBOARD_RATES: 'Rate review dashboard', DASHBOARD_SUBMISSIONS: 'Dashboard', RATES_SUMMARY: 'Rate summary', + RATE_EDIT: 'Edit rate', SUBMISSIONS: 'Submissions', SUBMISSIONS_NEW: 'New submission', SUBMISSIONS_EDIT_TOP_LEVEL: 'Submissions', diff --git a/services/app-web/src/constants/tealium.ts b/services/app-web/src/constants/tealium.ts index 02dbad6468..030a7659ff 100644 --- a/services/app-web/src/constants/tealium.ts +++ b/services/app-web/src/constants/tealium.ts @@ -38,6 +38,7 @@ const CONTENT_TYPE_BY_ROUTE: Record = { GRAPHQL_EXPLORER: 'dev', SETTINGS: 'table', RATES_SUMMARY: 'summary', + RATE_EDIT: 'form', SUBMISSIONS: 'form', SUBMISSIONS_NEW: 'form', SUBMISSIONS_EDIT_TOP_LEVEL: 'form', diff --git a/services/app-web/src/pages/App/AppRoutes.tsx b/services/app-web/src/pages/App/AppRoutes.tsx index 81f1b33d42..58b1a9053f 100644 --- a/services/app-web/src/pages/App/AppRoutes.tsx +++ b/services/app-web/src/pages/App/AppRoutes.tsx @@ -38,6 +38,7 @@ import { } from '../QuestionResponse' import { GraphQLExplorer } from '../GraphQLExplorer/GraphQLExplorer' import { RateSummary } from '../SubmissionSummary/RateSummary' +import { RateEdit } from '../RateEdit/RateEdit' function componentForAuthMode( authMode: AuthModeType @@ -75,7 +76,7 @@ const StateUserRoutes = ({ }): React.ReactElement => { // feature flag const ldClient = useLDClient() - const showRateSummaryPage: boolean = ldClient?.variation( + const showRatePages: boolean = ldClient?.variation( featureFlags.RATE_EDIT_UNLOCK.flag, featureFlags.RATE_EDIT_UNLOCK.defaultValue ) @@ -106,7 +107,13 @@ const StateUserRoutes = ({ path={RoutesRecord.SUBMISSIONS_NEW} element={} /> - {showRateSummaryPage && ( + {showRatePages && ( + } + /> + )} + {showRatePages && ( } diff --git a/services/app-web/src/pages/RateEdit/RateEdit.test.tsx b/services/app-web/src/pages/RateEdit/RateEdit.test.tsx new file mode 100644 index 0000000000..420d69c305 --- /dev/null +++ b/services/app-web/src/pages/RateEdit/RateEdit.test.tsx @@ -0,0 +1,48 @@ +import { screen, waitFor } from '@testing-library/react' +import { renderWithProviders } from "../../testHelpers" +import { RateEdit } from "./RateEdit" +import { fetchCurrentUserMock, fetchRateMockSuccess, mockValidStateUser } from "../../testHelpers/apolloMocks" +import { RoutesRecord } from '../../constants' +import { Route, Routes } from 'react-router-dom' + +// Wrap test component in some top level routes to allow getParams to be tested +const wrapInRoutes = (children: React.ReactNode) => { + return ( + + + + ) +} + +describe('RateEdit', () => { + afterAll(() => jest.clearAllMocks()) + + describe('Viewing RateEdit as a state user', () => { + + it('renders without errors', async () => { + renderWithProviders(wrapInRoutes(), { + apolloProvider: { + mocks: [ + fetchCurrentUserMock({ + user: mockValidStateUser(), + statusCode: 200, + }), + fetchRateMockSuccess({ rate: { id: '1337', status: 'UNLOCKED' } }), + ], + }, + routerProvider: { + route: '/rates/1337/edit' + }, + featureFlags: { + 'rate-edit-unlock': true + } + }) + + await waitFor(() => { + expect(screen.queryByTestId('rate-edit')).toBeInTheDocument() + }) + }) + }) +}) + + diff --git a/services/app-web/src/pages/RateEdit/RateEdit.tsx b/services/app-web/src/pages/RateEdit/RateEdit.tsx new file mode 100644 index 0000000000..af04cbfb00 --- /dev/null +++ b/services/app-web/src/pages/RateEdit/RateEdit.tsx @@ -0,0 +1,52 @@ +import React from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { useFetchRateQuery } from "../../gen/gqlClient"; +import { GridContainer } from "@trussworks/react-uswds"; +import { Loading } from "../../components"; +import { GenericErrorPage } from "../Errors/GenericErrorPage"; + +type RouteParams = { + id: string +} + +export const RateEdit = (): React.ReactElement => { + const navigate = useNavigate() + const { id } = useParams() + if (!id) { + throw new Error( + 'PROGRAMMING ERROR: id param not set in state submission form.' + ) + } + + const { data, loading, error } = useFetchRateQuery({ + variables: { + input: { + rateID: id, + }, + }, + }) + + const rate = data?.fetchRate.rate + + if (loading) { + return ( + + + + ) + } else if (error || !rate ) { + return + } + + if (rate.status !== 'UNLOCKED') { + navigate(`/rates/${id}`) + } + + return ( +

+ You've reached the '/rates/:id/edit' url placeholder for the incoming standalone edit rate form +
+ Ticket: MCR-3771 +

+ ) +} \ No newline at end of file diff --git a/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.test.tsx b/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.test.tsx index 75ae0e088c..490f69e143 100644 --- a/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.test.tsx +++ b/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.test.tsx @@ -9,6 +9,7 @@ import { import { RateSummary } from './RateSummary' import { RoutesRecord } from '../../../constants' import { Route, Routes } from 'react-router-dom' +import { RateEdit } from '../../RateEdit/RateEdit' // Wrap test component in some top level routes to allow getParams to be tested const wrapInRoutes = (children: React.ReactNode) => { @@ -114,7 +115,7 @@ describe('RateSummary', () => { }) describe('Viewing RateSummary as a State user', () => { - it('renders without errors', async () => { + it('renders SingleRateSummarySection component without errors for locked rate', async () => { renderWithProviders(wrapInRoutes(), { apolloProvider: { mocks: [ @@ -142,6 +143,41 @@ describe('RateSummary', () => { ).toBeInTheDocument() }) + it('redirects to RateEdit component from RateSummary without errors for unlocked rate', async () => { + renderWithProviders( + + } + /> + } + /> + , + { + apolloProvider: { + mocks: [ + fetchCurrentUserMock({ + user: mockValidStateUser(), + statusCode: 200, + }), + fetchRateMockSuccess({ rate: { id: '1337', status: 'UNLOCKED' } }), + ], + }, + routerProvider: { + route: '/rates/1337' + }, + featureFlags: { + 'rate-edit-unlock': true + } + }) + + await waitFor(() => { + expect(screen.queryByTestId('rate-edit')).toBeInTheDocument() + }) + }) + it('renders expected error page when rate ID is invalid', async () => { renderWithProviders(wrapInRoutes(), { apolloProvider: { diff --git a/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.tsx b/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.tsx index b0555c5116..fc0855602f 100644 --- a/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.tsx +++ b/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.tsx @@ -1,6 +1,6 @@ import { GridContainer, Icon, Link } from '@trussworks/react-uswds' import React, { useEffect, useState } from 'react' -import { NavLink, useParams } from 'react-router-dom' +import { NavLink, useNavigate, useParams } from 'react-router-dom' import { Loading } from '../../../components' import { usePage } from '../../../contexts/PageContext' @@ -19,6 +19,7 @@ export const RateSummary = (): React.ReactElement => { // Page level state const { loggedInUser } = useAuth() const { updateHeading } = usePage() + const navigate = useNavigate() const [rateName, setRateName] = useState(undefined) const { id } = useParams() if (!id) { @@ -52,6 +53,11 @@ export const RateSummary = (): React.ReactElement => { return } + //Redirecting a state user to the edit page if rate is unlocked + if (loggedInUser?.role === 'STATE_USER' && rate.status === 'UNLOCKED') { + navigate(`/rates/${id}/edit`) + } + if ( rateName !== currentRateRev.formData.rateCertificationName && currentRateRev.formData.rateCertificationName @@ -68,8 +74,7 @@ export const RateSummary = (): React.ReactElement => {