diff --git a/src/applications/representative-search/actions/reports/submitRepresentativeReport.js b/src/applications/representative-search/actions/reports/submitRepresentativeReport.js index f8295188e87e..b9ad630dbf31 100644 --- a/src/applications/representative-search/actions/reports/submitRepresentativeReport.js +++ b/src/applications/representative-search/actions/reports/submitRepresentativeReport.js @@ -63,7 +63,7 @@ export const submitRepresentativeReport = newReport => { } catch (error) { Sentry.withScope(scope => { scope.setExtra('error', error); - Sentry.captureMessage('Error fetching accredited representatives'); + Sentry.captureMessage('Error submitting representative report'); }); dispatch({ diff --git a/src/applications/representative-search/api/RepresentativeFinderApi.js b/src/applications/representative-search/api/RepresentativeFinderApi.js index 6074a8e8f997..26e5cebf6d00 100644 --- a/src/applications/representative-search/api/RepresentativeFinderApi.js +++ b/src/applications/representative-search/api/RepresentativeFinderApi.js @@ -1,4 +1,9 @@ -import { fetchAndUpdateSessionExpiration as fetch } from '@department-of-veterans-affairs/platform-utilities/api'; +/* eslint-disable camelcase */ + +import { + fetchAndUpdateSessionExpiration as fetch, + apiRequest, +} from '@department-of-veterans-affairs/platform-utilities/api'; import { getApi, resolveParamsWithUrl, endpointOptions } from '../config'; class RepresentativeFinderApi { @@ -41,6 +46,9 @@ class RepresentativeFinderApi { if (!response.ok) { throw Error(response.statusText); } + const csrf = response.headers.get('X-CSRF-Token'); + localStorage.setItem('csrfToken', csrf); + return response.json(); }) .then(res => { @@ -57,30 +65,16 @@ class RepresentativeFinderApi { } static reportResult(newReport) { - const reportRequestBody = { - representativeId: newReport.representativeId, - flags: [], - }; - const startTime = new Date().getTime(); - for (const [flagType, flaggedValue] of Object.entries(newReport.reports)) { - if (flaggedValue !== null) { - reportRequestBody.flags.push({ - flagType, - flaggedValue, - }); - } - } - const { requestUrl, apiSettings } = getApi( endpointOptions.flagReps, 'POST', - reportRequestBody, + newReport, ); return new Promise((resolve, reject) => { - fetch(requestUrl, apiSettings) + apiRequest(requestUrl, apiSettings) .then(response => { if (!response.ok) { throw Error(response.statusText); diff --git a/src/applications/representative-search/components/results/ReportModal.jsx b/src/applications/representative-search/components/results/ReportModal.jsx index 7f91b80c07b7..f6666fa8975a 100644 --- a/src/applications/representative-search/components/results/ReportModal.jsx +++ b/src/applications/representative-search/components/results/ReportModal.jsx @@ -1,10 +1,11 @@ +/* eslint-disable camelcase */ + import React, { useState } from 'react'; import PropTypes from 'prop-types'; import { VaModal, VaCheckboxGroup, } from '@department-of-veterans-affairs/component-library/dist/react-bindings'; -import { snakeCase } from 'lodash'; const ReportModal = ({ representativeName, @@ -78,17 +79,15 @@ const ReportModal = ({ }; const onSubmitModal = () => { - const formattedReportObject = { representativeId, reports: {} }; + const formattedReportObject = { + representativeId, + reports: {}, + }; // push non-null items to reports object Object.keys(reportObject).forEach(prop => { if (reportObject[prop] !== null) { - if (prop === 'phone') { - formattedReportObject.reports[snakeCase('phoneNumber')] = - reportObject.phone; - } else { - formattedReportObject.reports[prop] = reportObject[prop]; - } + formattedReportObject.reports[prop] = reportObject[prop]; } }); diff --git a/src/applications/representative-search/components/results/SearchResult.jsx b/src/applications/representative-search/components/results/SearchResult.jsx index a900421a47e6..816367820f62 100644 --- a/src/applications/representative-search/components/results/SearchResult.jsx +++ b/src/applications/representative-search/components/results/SearchResult.jsx @@ -1,6 +1,9 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; -import { scrollTo } from 'platform/utilities/ui'; +import { + focusElement, + scrollTo, +} from '@department-of-veterans-affairs/platform-utilities/ui'; import ReportModal from './ReportModal'; import { parsePhoneNumber } from '../../utils/phoneNumbers'; @@ -24,9 +27,9 @@ const SearchResult = ({ }) => { const [reportModalIsShowing, setReportModalIsShowing] = useState(false); - const { contact, extension } = parsePhoneNumber(phone); + const prevReportCount = useRef(reports?.length || 0); - const scrollElementId = `result-${representativeId}`; + const { contact, extension } = parsePhoneNumber(phone); const addressExists = addressLine1 || city || stateCode || zipCode; @@ -45,9 +48,20 @@ const SearchResult = ({ const closeReportModal = () => { setReportModalIsShowing(false); - scrollTo(scrollElementId); + scrollTo(`#report-button-${representativeId}`); + focusElement(`#report-button-${representativeId}`); }; + useEffect( + () => { + if (reports?.length > prevReportCount) { + scrollTo(`#thank-you-alert-${representativeId}`); + focusElement(`#thank-you-alert-${representativeId}`); + } + }, + [reports], + ); + return (
{/* Trigger methods for unit testing - temporary workaround for shadow root issues */} @@ -151,16 +165,20 @@ const SearchResult = ({ {reports && (
-

+

Thanks for reporting outdated information.

@@ -171,6 +189,8 @@ const SearchResult = ({ onClick={() => { setReportModalIsShowing(true); }} + tabIndex={-1} + id={`report-button-${representativeId}`} secondary text="Report outdated information" uswds diff --git a/src/applications/representative-search/config.js b/src/applications/representative-search/config.js index 5ee5a07ee58f..763d4fe9f59d 100644 --- a/src/applications/representative-search/config.js +++ b/src/applications/representative-search/config.js @@ -39,6 +39,25 @@ const baseUrl = ? `https://staging-api.va.gov` : `${environment.API_URL}`; +export const formatReportBody = newReport => { + const reportRequestBody = { + representative_id: newReport.representativeId, + flags: [], + }; + + for (const [flag_type, flagged_value] of Object.entries(newReport.reports)) { + if (flagged_value !== null) { + reportRequestBody.flags.push({ + // convert 'phone' to snakecase 'phone_number' before pushing + flag_type: flag_type === 'phone' ? 'phone_number' : flag_type, + flagged_value, + }); + } + } + + return reportRequestBody; +}; + /** * Build requestUrl and settings for api calls * * @param endpoint {String} eg '/vso_accredited_representatives' @@ -51,6 +70,12 @@ export const getApi = (endpoint, method = 'GET', requestBody) => { const csrfToken = localStorage.getItem('csrfToken'); + let formattedReportBody; + + if (method === 'POST') { + formattedReportBody = formatReportBody(requestBody); + } + const apiSettings = { mode: 'cors', method, @@ -66,7 +91,7 @@ export const getApi = (endpoint, method = 'GET', requestBody) => { // undefined for all requests that use this config. 'Source-App-Name': manifest.entryName, }, - body: JSON.stringify(requestBody) || null, + body: JSON.stringify(formattedReportBody) || null, }; return { requestUrl, apiSettings }; diff --git a/src/applications/representative-search/containers/App.jsx b/src/applications/representative-search/containers/App.jsx index 63ea358dd3dc..db732857d3c5 100644 --- a/src/applications/representative-search/containers/App.jsx +++ b/src/applications/representative-search/containers/App.jsx @@ -16,9 +16,7 @@ function App({ children }) { TOGGLE_NAMES, } = useFeatureToggle(); - const appEnabled = useToggleValue( - TOGGLE_NAMES.findARepresentativeEnableFrontend, - ); + const appEnabled = useToggleValue(TOGGLE_NAMES.findARepresentativeEnabled); const togglesLoading = useToggleLoadingValue(); diff --git a/src/applications/representative-search/mocks/feature-toggles/index.js b/src/applications/representative-search/mocks/feature-toggles/index.js index f19ead79155d..9505745baa99 100644 --- a/src/applications/representative-search/mocks/feature-toggles/index.js +++ b/src/applications/representative-search/mocks/feature-toggles/index.js @@ -1,13 +1,13 @@ const generateFeatureToggles = (toggles = {}) => { - const { findARepresentativeEnableFrontend = false } = toggles; + const { findARepresentativeEnabled = false } = toggles; return { data: { type: 'feature_toggles', features: [ { - name: 'find_a_representative_enable_frontend', - value: findARepresentativeEnableFrontend, + name: 'find_a_representative', + value: findARepresentativeEnabled, }, ], }, diff --git a/src/applications/representative-search/tests/api-url-parameters.railsEngine.unit.spec.js b/src/applications/representative-search/tests/api-url-parameters.railsEngine.unit.spec.js index 6d48678522d6..7c280e870a76 100644 --- a/src/applications/representative-search/tests/api-url-parameters.railsEngine.unit.spec.js +++ b/src/applications/representative-search/tests/api-url-parameters.railsEngine.unit.spec.js @@ -1,6 +1,11 @@ import { expect } from 'chai'; import environment from '@department-of-veterans-affairs/platform-utilities/environment'; -import { resolveParamsWithUrl, getApi, endpointOptions } from '../config'; +import { + resolveParamsWithUrl, + getApi, + endpointOptions, + formatReportBody, +} from '../config'; describe('Locator url and parameters builder', () => { const address = '43210'; @@ -90,6 +95,24 @@ describe('Locator url and parameters builder', () => { expect(apiSettings?.headers?.['X-CSRF-Token']).to.eql('12345'); }); + it('should format report object into snake case POST request body', () => { + const reportObject = { + representativeId: 123, + reports: { + phone: '644-465-8493', + email: 'example@rep.com', + address: '123 Any Street', + other: 'other comment', + }, + }; + + const formattedReportBody = JSON.stringify(formatReportBody(reportObject)); + + expect(formattedReportBody).to.eql( + '{"representative_id":123,"flags":[{"flag_type":"phone_number","flagged_value":"644-465-8493"},{"flag_type":"email","flagged_value":"example@rep.com"},{"flag_type":"address","flagged_value":"123 Any Street"},{"flag_type":"other","flagged_value":"other comment"}]}', + ); + }); + it('should exclude null params from request', () => { const { requestUrl } = getApi(endpointOptions.fetchOtherReps); diff --git a/src/applications/representative-search/tests/e2e/accessibility.cypress.spec.js b/src/applications/representative-search/tests/e2e/accessibility.cypress.spec.js index 84600fdd4982..2c4e2c8ab2b6 100644 --- a/src/applications/representative-search/tests/e2e/accessibility.cypress.spec.js +++ b/src/applications/representative-search/tests/e2e/accessibility.cypress.spec.js @@ -7,9 +7,7 @@ describe('Accessibility', () => { cy.viewport(1200, 700); cy.intercept('GET', '/v0/feature_toggles*', { data: { - features: [ - { name: 'find_a_representative_enable_frontend', value: true }, - ], + features: [{ name: 'find_a_representative_enabled', value: true }], }, }); cy.intercept('GET', '/v0/maintenance_windows', []); diff --git a/src/applications/representative-search/tests/e2e/geolocation.cypress.spec.js b/src/applications/representative-search/tests/e2e/geolocation.cypress.spec.js index ac6d1693e708..9e825130aead 100644 --- a/src/applications/representative-search/tests/e2e/geolocation.cypress.spec.js +++ b/src/applications/representative-search/tests/e2e/geolocation.cypress.spec.js @@ -20,9 +20,7 @@ describe('User geolocation', () => { cy.intercept('GET', '/geocoding/**/*', mockLaLocation).as('caLocation'); cy.intercept('GET', '/v0/feature_toggles*', { data: { - features: [ - { name: 'find_a_representative_enable_frontend', value: true }, - ], + features: [{ name: 'find_a_representative_enabled', value: true }], }, }); cy.visit('/get-help-from-accredited-representative/find-rep/'); diff --git a/src/applications/representative-search/tests/e2e/mobile.cypress.spec.js b/src/applications/representative-search/tests/e2e/mobile.cypress.spec.js index 4cb8b049cc18..5bda6eb42240 100644 --- a/src/applications/representative-search/tests/e2e/mobile.cypress.spec.js +++ b/src/applications/representative-search/tests/e2e/mobile.cypress.spec.js @@ -17,9 +17,7 @@ describe('Mobile', () => { beforeEach(() => { cy.intercept('GET', '/v0/feature_toggles*', { data: { - features: [ - { name: 'find_a_representative_enable_frontend', value: true }, - ], + features: [{ name: 'find_a_representative_enabled', value: true }], }, }); cy.intercept('GET', '/v0/maintenance_windows', []); diff --git a/src/applications/representative-search/tests/e2e/representativeSearch.cypress.spec.js b/src/applications/representative-search/tests/e2e/representativeSearch.cypress.spec.js index fab1efc08aad..4cc24fde63dc 100644 --- a/src/applications/representative-search/tests/e2e/representativeSearch.cypress.spec.js +++ b/src/applications/representative-search/tests/e2e/representativeSearch.cypress.spec.js @@ -30,9 +30,7 @@ describe('Representative Search', () => { beforeEach(() => { cy.intercept('GET', '/v0/feature_toggles*', { data: { - features: [ - { name: 'find_a_representative_enable_frontend', value: true }, - ], + features: [{ name: 'find_a_representative_enabled', value: true }], }, }); cy.intercept('GET', '/v0/maintenance_windows', []); diff --git a/src/applications/representative-search/tests/e2e/serverErrors.cypress.spec.js b/src/applications/representative-search/tests/e2e/serverErrors.cypress.spec.js index 1f4d96a50169..40193f370b0f 100644 --- a/src/applications/representative-search/tests/e2e/serverErrors.cypress.spec.js +++ b/src/applications/representative-search/tests/e2e/serverErrors.cypress.spec.js @@ -4,9 +4,7 @@ describe('Find a Representative error handling', () => { beforeEach(() => { cy.intercept('GET', '/v0/feature_toggles*', { data: { - features: [ - { name: 'find_a_representative_enable_frontend', value: true }, - ], + features: [{ name: 'find_a_representative_enabled', value: true }], }, }); cy.intercept('GET', '/v0/maintenance_windows', []); diff --git a/src/platform/utilities/feature-toggles/featureFlagNames.js b/src/platform/utilities/feature-toggles/featureFlagNames.js index eb6654c2b3a3..67bef0c617cb 100644 --- a/src/platform/utilities/feature-toggles/featureFlagNames.js +++ b/src/platform/utilities/feature-toggles/featureFlagNames.js @@ -98,6 +98,7 @@ export default Object.freeze({ financialStatusReportReviewPageNavigation: 'financial_status_report_review_page_navigation', findARepresentativeEnableFrontend: 'find_a_representative_enable_frontend', + findARepresentativeEnabled: 'find_a_representative_enabled', form2010206: 'form2010206', form2110210: 'form2110210', form210845: 'form210845',