From c15ea63554f728e6e6c6d73b5a48b27b3021dcfd Mon Sep 17 00:00:00 2001 From: Virginia Czosek Date: Tue, 16 Jun 2026 14:14:06 -0700 Subject: [PATCH 1/6] Move config variables to vite env file --- front-end/{src/config.ts => .env} | 10 ++-- front-end/cypress/helpers/modalHelpers.ts | 9 ++-- .../src/components/Error/ErrorMessage.tsx | 8 +-- .../src/components/Modal/DownloadModal.tsx | 11 ++--- .../src/components/Modal/WarningModal.tsx | 5 +- front-end/src/pages/rootRoute.tsx | 49 ++++++++++--------- 6 files changed, 48 insertions(+), 44 deletions(-) rename front-end/{src/config.ts => .env} (76%) diff --git a/front-end/src/config.ts b/front-end/.env similarity index 76% rename from front-end/src/config.ts rename to front-end/.env index ea2ceae0..74804dfc 100644 --- a/front-end/src/config.ts +++ b/front-end/.env @@ -1,10 +1,12 @@ -export const ADMIN_EMAIL = '' +VITE_ADMIN_EMAIL = '' -export const DOWNLOAD_ACKNOWLEDGMENT_TEXT = +VITE_DOWNLOAD_ACKNOWLEDGMENT_TEXT = 'I understand that by downloading data from this system, I will be accessing Personally Identifiable Information (PII) and Confidential Information (CI).' -export const DOWNLOAD_ACKNOWLEDGMENT_LABEL = +VITE_DOWNLOAD_ACKNOWLEDGMENT_LABEL = 'I confirm that I am knowingly downloading PII or CI and understand that I am responsible for safeguarding this data.' -export const PII_WARNING_TEXT = +VITE_PII_WARNING_TEXT = 'I understand that by using this system, I will be accessing Personally Identifiable Information (PII) and Confidential Information (CI) and I understand my responsibilities to safeguard all this information.' + +VITE_SHOW_CFPB_HEADER = false \ No newline at end of file diff --git a/front-end/cypress/helpers/modalHelpers.ts b/front-end/cypress/helpers/modalHelpers.ts index 03b41a43..2447da26 100644 --- a/front-end/cypress/helpers/modalHelpers.ts +++ b/front-end/cypress/helpers/modalHelpers.ts @@ -1,7 +1,8 @@ -import { - DOWNLOAD_ACKNOWLEDGMENT_LABEL, - DOWNLOAD_ACKNOWLEDGMENT_TEXT -} from '@src/config' +const DOWNLOAD_ACKNOWLEDGMENT_TEXT = + 'I understand that by downloading data from this system, I will be accessing Personally Identifiable Information (PII) and Confidential Information (CI).' + +const DOWNLOAD_ACKNOWLEDGMENT_LABEL = + 'I confirm that I am knowingly downloading PII or CI and understand that I am responsible for safeguarding this data.' export const getInputByLabel = (label: string) => { return cy diff --git a/front-end/src/components/Error/ErrorMessage.tsx b/front-end/src/components/Error/ErrorMessage.tsx index 734cb939..172d9f42 100644 --- a/front-end/src/components/Error/ErrorMessage.tsx +++ b/front-end/src/components/Error/ErrorMessage.tsx @@ -1,5 +1,4 @@ import { ButtonGroup, Link as CFPBLink } from '@cfpb/design-system-react' -import { ADMIN_EMAIL } from '@src/config' import { Link } from '@src/utils/DSRLink' import { useRouterState } from '@tanstack/react-router' import type { ReactElement } from 'react' @@ -18,6 +17,7 @@ export default function ErrorMessage({ }: ErrorMessageProperties): ReactElement { const router = useRouterState() const currentPath = router.location.pathname + const adminEmail = import.meta.env.VITE_ADMIN_EMAIL as string return (
@@ -30,12 +30,12 @@ export default function ErrorMessage({ Back to Metro 2 home page - {ADMIN_EMAIL ? ( + {adminEmail ? ( Contact an administrator diff --git a/front-end/src/components/Modal/DownloadModal.tsx b/front-end/src/components/Modal/DownloadModal.tsx index 94e804eb..8238bba9 100644 --- a/front-end/src/components/Modal/DownloadModal.tsx +++ b/front-end/src/components/Modal/DownloadModal.tsx @@ -1,8 +1,4 @@ import { Button, ButtonGroup, Checkbox } from '@cfpb/design-system-react' -import { - DOWNLOAD_ACKNOWLEDGMENT_LABEL, - DOWNLOAD_ACKNOWLEDGMENT_TEXT -} from '@src/config' import DOMPurify from 'dompurify' import type { ReactElement } from 'react' import { useState } from 'react' @@ -21,7 +17,10 @@ interface DownloadModalProperties { buttonText?: string } -const downloadText = DOMPurify.sanitize(DOWNLOAD_ACKNOWLEDGMENT_TEXT) +const downloadText = DOMPurify.sanitize( + import.meta.env.VITE_DOWNLOAD_ACKNOWLEDGMENT_TEXT as string +) +const downloadLabel = import.meta.env.VITE_DOWNLOAD_ACKNOWLEDGMENT_LABEL as string /** * DownloadModal() @@ -93,7 +92,7 @@ export default function DownloadModal({ isLarge checked={privacyMessageAcknowledged} data-testid='pii-checkbox' - label={DOWNLOAD_ACKNOWLEDGMENT_LABEL} + label={downloadLabel} onChange={onChange} />
diff --git a/front-end/src/components/Modal/WarningModal.tsx b/front-end/src/components/Modal/WarningModal.tsx index 7074b9af..3e517339 100644 --- a/front-end/src/components/Modal/WarningModal.tsx +++ b/front-end/src/components/Modal/WarningModal.tsx @@ -1,12 +1,13 @@ import { Button } from '@cfpb/design-system-react' -import { PII_WARNING_TEXT } from '@src/config' import { acceptPIIWarning, hasAcceptedPIIWarning } from '@src/utils/cookies' import DOMPurify from 'dompurify' import type { ReactElement } from 'react' import { useState } from 'react' import { Modal, ModalFooter } from './Modal' -const warningText = DOMPurify.sanitize(PII_WARNING_TEXT) +const warningText = DOMPurify.sanitize( + import.meta.env.VITE_PII_WARNING_TEXT as string +) export default function WarningModal(): ReactElement | null { const [isOpen, setIsOpen] = useState(!hasAcceptedPIIWarning()) diff --git a/front-end/src/pages/rootRoute.tsx b/front-end/src/pages/rootRoute.tsx index 8c77f154..4498a6aa 100644 --- a/front-end/src/pages/rootRoute.tsx +++ b/front-end/src/pages/rootRoute.tsx @@ -9,33 +9,34 @@ interface RouterContext { queryClient: QueryClient } +const showCFPBHeader = import.meta.env.VITE_SHOW_CFPB_HEADER as boolean + const rootRoute = createRootRouteWithContext()({ component: (): React.JSX.Element => ( }> - - User guide - - ]} - /> - {/* - TODO: use env variable to toggle CFPB and generic header - const isInternal: boolean | null = import.meta.env.VITE_CFPB_INTERNAL as boolean -
-

- - Metro2 Evaluator Tool - -

-
- - Need help? See the user guide - -
-
*/} - + {showCFPBHeader === true ? ( + + User guide + + ]} + /> + ) : ( +
+

+ + Metro2 Evaluator Tool + +

+
+ + Need help? See the user guide + +
+
+ )}
From ef638a13b504f569a20b0c8a435b12f5f231bed8 Mon Sep 17 00:00:00 2001 From: Virginia Czosek Date: Tue, 16 Jun 2026 17:36:56 -0700 Subject: [PATCH 2/6] Add typescript declaration file for vite env variables --- front-end/src/components/Error/ErrorMessage.tsx | 3 +-- front-end/src/components/Modal/DownloadModal.tsx | 4 ++-- front-end/src/components/Modal/WarningModal.tsx | 4 +--- front-end/src/pages/rootRoute.tsx | 4 ++-- front-end/src/vite-env.d.ts | 13 +++++++++++++ 5 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 front-end/src/vite-env.d.ts diff --git a/front-end/src/components/Error/ErrorMessage.tsx b/front-end/src/components/Error/ErrorMessage.tsx index 172d9f42..7d79c5d4 100644 --- a/front-end/src/components/Error/ErrorMessage.tsx +++ b/front-end/src/components/Error/ErrorMessage.tsx @@ -17,7 +17,7 @@ export default function ErrorMessage({ }: ErrorMessageProperties): ReactElement { const router = useRouterState() const currentPath = router.location.pathname - const adminEmail = import.meta.env.VITE_ADMIN_EMAIL as string + const adminEmail = import.meta.env.VITE_ADMIN_EMAIL return (
@@ -34,7 +34,6 @@ export default function ErrorMessage({ Contact an administrator diff --git a/front-end/src/components/Modal/DownloadModal.tsx b/front-end/src/components/Modal/DownloadModal.tsx index 8238bba9..bf52fca0 100644 --- a/front-end/src/components/Modal/DownloadModal.tsx +++ b/front-end/src/components/Modal/DownloadModal.tsx @@ -18,9 +18,9 @@ interface DownloadModalProperties { } const downloadText = DOMPurify.sanitize( - import.meta.env.VITE_DOWNLOAD_ACKNOWLEDGMENT_TEXT as string + import.meta.env.VITE_DOWNLOAD_ACKNOWLEDGMENT_TEXT ) -const downloadLabel = import.meta.env.VITE_DOWNLOAD_ACKNOWLEDGMENT_LABEL as string +const downloadLabel = import.meta.env.VITE_DOWNLOAD_ACKNOWLEDGMENT_LABEL /** * DownloadModal() diff --git a/front-end/src/components/Modal/WarningModal.tsx b/front-end/src/components/Modal/WarningModal.tsx index 3e517339..79d07672 100644 --- a/front-end/src/components/Modal/WarningModal.tsx +++ b/front-end/src/components/Modal/WarningModal.tsx @@ -5,9 +5,7 @@ import type { ReactElement } from 'react' import { useState } from 'react' import { Modal, ModalFooter } from './Modal' -const warningText = DOMPurify.sanitize( - import.meta.env.VITE_PII_WARNING_TEXT as string -) +const warningText = DOMPurify.sanitize(import.meta.env.VITE_PII_WARNING_TEXT) export default function WarningModal(): ReactElement | null { const [isOpen, setIsOpen] = useState(!hasAcceptedPIIWarning()) diff --git a/front-end/src/pages/rootRoute.tsx b/front-end/src/pages/rootRoute.tsx index 4498a6aa..1d1f7512 100644 --- a/front-end/src/pages/rootRoute.tsx +++ b/front-end/src/pages/rootRoute.tsx @@ -9,12 +9,12 @@ interface RouterContext { queryClient: QueryClient } -const showCFPBHeader = import.meta.env.VITE_SHOW_CFPB_HEADER as boolean +const showCFPBHeader = import.meta.env.VITE_SHOW_CFPB_HEADER const rootRoute = createRootRouteWithContext()({ component: (): React.JSX.Element => ( }> - {showCFPBHeader === true ? ( + {showCFPBHeader === 'true' ? ( + +interface ImportMetaEnv { + readonly VITE_SHOW_CFPB_HEADER: string // Cast to string since Vite delivers it as a string + readonly VITE_ADMIN_EMAIL: string + readonly VITE_DOWNLOAD_ACKNOWLEDGMENT_TEXT: string + readonly VITE_DOWNLOAD_ACKNOWLEDGMENT_LABEL: string + readonly VITE_PII_WARNING_TEXT: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} From 5419b47377442f794117973cfe2bc7f147c11ce9 Mon Sep 17 00:00:00 2001 From: Virginia Czosek Date: Wed, 17 Jun 2026 08:33:51 -0700 Subject: [PATCH 3/6] Add vite env variables to docker-compose. --- docker-compose.yml | 5 +++++ front-end/.env | 3 --- front-end/src/components/Modal/DownloadModal.tsx | 3 +-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e634128f..56f0e13e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,6 +23,11 @@ services: frontend: container_name: frontend + environment: + - VITE_SHOW_CFPB_HEADER + - VITE_ADMIN_EMAIL + - VITE_DOWNLOAD_ACKNOWLEDGMENT_TEXT + - VITE_PII_WARNING_TEXT build: context: ./front-end target: dev diff --git a/front-end/.env b/front-end/.env index 74804dfc..59df4d75 100644 --- a/front-end/.env +++ b/front-end/.env @@ -3,9 +3,6 @@ VITE_ADMIN_EMAIL = '' VITE_DOWNLOAD_ACKNOWLEDGMENT_TEXT = 'I understand that by downloading data from this system, I will be accessing Personally Identifiable Information (PII) and Confidential Information (CI).' -VITE_DOWNLOAD_ACKNOWLEDGMENT_LABEL = - 'I confirm that I am knowingly downloading PII or CI and understand that I am responsible for safeguarding this data.' - VITE_PII_WARNING_TEXT = 'I understand that by using this system, I will be accessing Personally Identifiable Information (PII) and Confidential Information (CI) and I understand my responsibilities to safeguard all this information.' diff --git a/front-end/src/components/Modal/DownloadModal.tsx b/front-end/src/components/Modal/DownloadModal.tsx index bf52fca0..aeb1a989 100644 --- a/front-end/src/components/Modal/DownloadModal.tsx +++ b/front-end/src/components/Modal/DownloadModal.tsx @@ -20,7 +20,6 @@ interface DownloadModalProperties { const downloadText = DOMPurify.sanitize( import.meta.env.VITE_DOWNLOAD_ACKNOWLEDGMENT_TEXT ) -const downloadLabel = import.meta.env.VITE_DOWNLOAD_ACKNOWLEDGMENT_LABEL /** * DownloadModal() @@ -92,7 +91,7 @@ export default function DownloadModal({ isLarge checked={privacyMessageAcknowledged} data-testid='pii-checkbox' - label={downloadLabel} + label='I confirm that I am knowingly downloading PII or CI and understand that I am responsible for safeguarding this data.' onChange={onChange} />
From c46100246059cc4c2888637abf3fc7069a7acd97 Mon Sep 17 00:00:00 2001 From: Virginia Czosek Date: Wed, 17 Jun 2026 14:54:16 -0700 Subject: [PATCH 4/6] Add warning modal tests --- .../cypress/e2e/general_warning-modal.cy.ts | 45 +++++++++++++++++++ front-end/cypress/helpers/modalHelpers.ts | 4 +- .../src/components/Modal/DownloadModal.tsx | 2 +- front-end/src/components/Modal/Modal.tsx | 7 +-- .../src/components/Modal/WarningModal.tsx | 8 ++-- front-end/src/vite-env.d.ts | 1 - 6 files changed, 57 insertions(+), 10 deletions(-) create mode 100644 front-end/cypress/e2e/general_warning-modal.cy.ts diff --git a/front-end/cypress/e2e/general_warning-modal.cy.ts b/front-end/cypress/e2e/general_warning-modal.cy.ts new file mode 100644 index 00000000..439aa7a2 --- /dev/null +++ b/front-end/cypress/e2e/general_warning-modal.cy.ts @@ -0,0 +1,45 @@ +import { Metro2Modal } from '@cypress/helpers/modalHelpers' +import { PII_COOKIE_NAME } from '@src/constants/settings' +const modal = new Metro2Modal() + +describe('Warning modal', () => { + it('Should show a warning modal when PII cookie is not set', () => { + cy.intercept('GET', '/api/users/', { fixture: 'user' }).as('getUser') + cy.visit('/') + cy.wait('@getUser') + // On page load, no PII warning cookie is set + cy.getCookie(PII_COOKIE_NAME).should('not.exist') + + // Without the cookie, the warning modal is displayed + modal.getModal('warning-modal').should('exist') + }) + + it('Should set a cookie when warning is accepted', () => { + cy.intercept('GET', '/api/users/', { fixture: 'user' }).as('getUser') + cy.visit('/') + cy.wait('@getUser') + + // On page load, no PII warning cookie is set + cy.getCookie(PII_COOKIE_NAME).should('not.exist') + + // The warning modal is displayed + modal + .getModal('warning-modal') + .should('exist') + .within(() => { + // Clicking the accept button adds a cookie + cy.findByTestId('accept-warning-button').click() + cy.getCookie(PII_COOKIE_NAME).should('have.property', 'value', 'true') + }) + }) +}) + +it('Should not show a warning modal when PII cookie is set', () => { + cy.setCookie(PII_COOKIE_NAME, 'true') + cy.intercept('GET', '/api/users/', { fixture: 'user' }).as('getUser') + cy.visit('/') + cy.wait('@getUser') + + // with the cookie, the warning modal is not displayed + modal.getModal('warning-modal').should('not.exist') +}) diff --git a/front-end/cypress/helpers/modalHelpers.ts b/front-end/cypress/helpers/modalHelpers.ts index 2447da26..799a4d3c 100644 --- a/front-end/cypress/helpers/modalHelpers.ts +++ b/front-end/cypress/helpers/modalHelpers.ts @@ -14,8 +14,8 @@ export const getInputByLabel = (label: string) => { } export class Metro2Modal { - getModal() { - return cy.get('.modal') + getModal(testid: string | null = null) { + return testid ? cy.get(`[data-testid="${testid}"].modal`) : cy.get('.modal') } openModal(buttonText: string): Cypress.Chainable { diff --git a/front-end/src/components/Modal/DownloadModal.tsx b/front-end/src/components/Modal/DownloadModal.tsx index aeb1a989..33417091 100644 --- a/front-end/src/components/Modal/DownloadModal.tsx +++ b/front-end/src/components/Modal/DownloadModal.tsx @@ -82,7 +82,7 @@ export default function DownloadModal({ {requirePrivacyAcknowledgment && (
+ data-testid='download-acknowledgment'> {privacyHeader}

diff --git a/front-end/src/components/Modal/Modal.tsx b/front-end/src/components/Modal/Modal.tsx index b192d3b9..23f406eb 100644 --- a/front-end/src/components/Modal/Modal.tsx +++ b/front-end/src/components/Modal/Modal.tsx @@ -14,8 +14,9 @@ export function Modal({ children, interactionRequired = false, open, - onClose -}: ModalProperties & React.HTMLAttributes): ReactElement | null { + onClose, + ...rest +}: ModalProperties & React.HTMLAttributes): ReactElement | null { const dialogRef = useRef(null) const openModal = (): void => { dialogRef.current?.showModal() @@ -51,7 +52,7 @@ export function Modal({ }) return ( - +
{interactionRequired ? ( '' diff --git a/front-end/src/components/Modal/WarningModal.tsx b/front-end/src/components/Modal/WarningModal.tsx index 79d07672..21671291 100644 --- a/front-end/src/components/Modal/WarningModal.tsx +++ b/front-end/src/components/Modal/WarningModal.tsx @@ -18,15 +18,17 @@ export default function WarningModal(): ReactElement | null { } return ( - +

Warning

-
+