-
Notifications
You must be signed in to change notification settings - Fork 125
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Appoint a rep - search representative screen (#31604)
* Added redux actions for rep search * Created SelectAccreditedRepresentative component * Added representative select page * Added rep search reducer * Refactored App container * API constant * API method for fetching representatives * Removed redux logic * Refactored SelectAccreditedRepresentative to make use of api method * Fixed request url * Initial search result components * Splitting non-vet and vets into two chapters * Added description to veteran-identification * Temporarily removing confirmClaimantPersonalInformation * Removed description from veteran personal information * Passing either name or fullName to search result * Added condition for veteran vs nonveteran * Removed key from proptypes * Removed unnecessary jsx * Testing for contact rather than phone --------- Co-authored-by: Ray Messina <48040208+rmessina1010@users.noreply.github.com>
- Loading branch information
1 parent
b113c13
commit d645092
Showing
11 changed files
with
511 additions
and
71 deletions.
There are no files selected for viewing
45 changes: 45 additions & 0 deletions
45
src/applications/representative-appoint/api/fetchRepresentatives.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import environment from 'platform/utilities/environment'; | ||
import { fetchAndUpdateSessionExpiration as fetch } from '@department-of-veterans-affairs/platform-utilities/api'; | ||
import { REPRESENTATIVES_API } from '../constants/api'; | ||
import manifest from '../manifest.json'; | ||
|
||
export const fetchRepresentatives = async ({ query }) => { | ||
const apiSettings = { | ||
mode: 'cors', | ||
method: 'GET', | ||
headers: { | ||
'X-Key-Inflection': 'camel', | ||
'Sec-Fetch-Mode': 'cors', | ||
'Content-Type': 'application/json', | ||
'Source-App-Name': manifest.entryName, | ||
}, | ||
}; | ||
|
||
const startTime = new Date().getTime(); | ||
|
||
const apiUrl = | ||
environment.BASE_URL === 'http://localhost:3001' | ||
? `https://staging-api.va.gov` | ||
: `${environment.API_URL}`; | ||
|
||
return new Promise((resolve, reject) => { | ||
fetch(`${apiUrl}${REPRESENTATIVES_API}?query=${query}`, apiSettings) | ||
.then(res => { | ||
if (!res.ok) { | ||
throw Error(res.statusText); | ||
} | ||
|
||
return res.json(); | ||
}) | ||
.then(res => { | ||
const endTime = new Date().getTime(); | ||
const resultTime = endTime - startTime; | ||
res.meta = { | ||
...res.meta, | ||
resultTime, | ||
}; | ||
return res; | ||
}) | ||
.then(data => resolve(data), error => reject(error)); | ||
}); | ||
}; |
173 changes: 173 additions & 0 deletions
173
src/applications/representative-appoint/components/SearchResult.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
// import { recordEvent } from '@department-of-veterans-affairs/platform-monitoring/exports'; | ||
import { parsePhoneNumber } from '../utilities/helpers'; | ||
|
||
const SearchResult = ({ | ||
representativeName, | ||
addressLine1, | ||
addressLine2, | ||
addressLine3, | ||
city, | ||
stateCode, | ||
zipCode, | ||
phone, | ||
distance, | ||
email, | ||
associatedOrgs, | ||
representativeId, | ||
query, | ||
}) => { | ||
const { contact, extension } = parsePhoneNumber(phone); | ||
|
||
const addressExists = addressLine1 || city || stateCode || zipCode; | ||
|
||
const address = | ||
[ | ||
(addressLine1 || '').trim(), | ||
(addressLine2 || '').trim(), | ||
(addressLine3 || '').trim(), | ||
] | ||
.filter(Boolean) | ||
.join(' ') + | ||
(city ? ` ${city},` : '') + | ||
(stateCode ? ` ${stateCode}` : '') + | ||
(zipCode ? ` ${zipCode}` : ''); | ||
|
||
// pending analytics event | ||
const recordContactLinkClick = () => { | ||
// recordEvent({ | ||
// // prettier-ignore | ||
// 'event': 'appoint-a-rep-search-results-click', | ||
// 'search-query': query?.locationQueryString, | ||
// 'search-filters-list': { | ||
// 'representative-type': query?.representativeType, | ||
// 'representative-name': query?.representativeQueryString, | ||
// }, | ||
// 'search-selection': 'Find VA Accredited Rep', | ||
// 'search-results-id': representativeId, | ||
// 'search-results-total-count': | ||
// searchResults?.meta?.pagination?.totalEntries, | ||
// 'search-results-total-pages': searchResults?.meta?.pagination?.totalPages, | ||
// 'search-result-position': key, | ||
// }); | ||
}; | ||
|
||
return ( | ||
<va-card class="representative-result-card vads-u-padding--4"> | ||
<div className="representative-result-card-content"> | ||
<div className="representative-info-heading"> | ||
{distance && ( | ||
<div | ||
id={`representative-${representativeId}`} | ||
className="vads-u-font-weight--bold vads-u-font-family--serif" | ||
> | ||
{parseFloat(JSON.parse(distance).toFixed(2))} Mi | ||
</div> | ||
)} | ||
{representativeName && ( | ||
<> | ||
<div | ||
className="vads-u-font-family--serif vads-u-margin-top--2p5" | ||
id={`result-${representativeId}`} | ||
> | ||
<h3 aria-describedby={`representative-${representativeId}`}> | ||
{representativeName} | ||
</h3> | ||
</div> | ||
{associatedOrgs?.length === 1 && ( | ||
<p style={{ marginTop: 0 }}>{associatedOrgs[0]}</p> | ||
)} | ||
</> | ||
)} | ||
</div> | ||
{associatedOrgs?.length > 1 && ( | ||
<div className="associated-organizations-info vads-u-margin-top--1p5"> | ||
<va-additional-info | ||
trigger="See associated organizations" | ||
disable-border | ||
uswds | ||
> | ||
{associatedOrgs?.map((org, index) => { | ||
return ( | ||
<> | ||
<p>{org}</p> | ||
{index < associatedOrgs.length - 1 ? ( | ||
<br style={{ lineHeight: '0.625rem' }} /> | ||
) : null} | ||
</> | ||
); | ||
})} | ||
</va-additional-info> | ||
</div> | ||
)} | ||
|
||
<div className="representative-contact-section vads-u-margin-top--3"> | ||
{addressExists && ( | ||
<div className="address-link"> | ||
<a | ||
href={`https://maps.google.com?saddr=${ | ||
query?.context?.location | ||
}&daddr=${address}`} | ||
tabIndex="0" | ||
className="address-anchor" | ||
onClick={() => recordContactLinkClick()} | ||
target="_blank" | ||
rel="noreferrer" | ||
aria-label={`${address} (opens in a new tab)`} | ||
> | ||
{addressLine1}{' '} | ||
{addressLine2 ? ( | ||
<> | ||
<br /> {addressLine2} | ||
</> | ||
) : null}{' '} | ||
<br /> | ||
{city}, {stateCode} {zipCode} | ||
</a> | ||
</div> | ||
)} | ||
{contact && ( | ||
<div className="vads-u-margin-top--1p5"> | ||
<va-telephone | ||
contact={contact} | ||
extension={extension} | ||
onClick={() => recordContactLinkClick()} | ||
disable-analytics | ||
/> | ||
</div> | ||
)} | ||
{email && ( | ||
<div className="vads-u-margin-top--1p5"> | ||
<a | ||
href={`mailto:${email}`} | ||
onClick={() => recordContactLinkClick()} | ||
> | ||
{email} | ||
</a> | ||
</div> | ||
)} | ||
</div> | ||
</div> | ||
</va-card> | ||
); | ||
}; | ||
|
||
SearchResult.propTypes = { | ||
addressLine1: PropTypes.string, | ||
addressLine2: PropTypes.string, | ||
addressLine3: PropTypes.string, | ||
associatedOrgs: PropTypes.array, | ||
city: PropTypes.string, | ||
distance: PropTypes.string, | ||
email: PropTypes.string, | ||
representativeName: PropTypes.string, | ||
phone: PropTypes.string, | ||
representativeId: PropTypes.string, | ||
stateCode: PropTypes.string, | ||
type: PropTypes.string, | ||
zipCode: PropTypes.string, | ||
}; | ||
|
||
export default SearchResult; |
113 changes: 113 additions & 0 deletions
113
src/applications/representative-appoint/components/SelectAccreditedRepresentative.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import React, { useState } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { | ||
VaButton, | ||
VaTextInput, | ||
} from '@department-of-veterans-affairs/component-library/dist/react-bindings'; | ||
import { fetchRepresentatives } from '../api/fetchRepresentatives'; | ||
import SearchResult from './SearchResult'; | ||
|
||
const SelectAccreditedRepresentative = () => { | ||
const [query, setQuery] = useState(''); | ||
const [loading, setLoading] = useState(false); | ||
const [error, setError] = useState(null); | ||
const [representatives, setRepresentatives] = useState([]); | ||
|
||
// const listProps = useMemo(() => ({ ...props, representatives, query }), [ | ||
// representatives, | ||
// props, | ||
// query, | ||
// ]); | ||
|
||
const handleChange = e => { | ||
setQuery(e.target.value); | ||
}; | ||
|
||
const handleClick = async () => { | ||
if (!query.trim()) { | ||
setError('Search for a representative'); | ||
return; | ||
} | ||
|
||
setLoading(true); | ||
setError(null); | ||
setRepresentatives(null); | ||
|
||
try { | ||
const representativeResults = await fetchRepresentatives({ query }); | ||
setRepresentatives(representativeResults); | ||
} catch (err) { | ||
setError(err.errorMessage); | ||
} finally { | ||
setLoading(false); | ||
} | ||
}; | ||
|
||
const searchResults = () => { | ||
if (loading) { | ||
return <va-loading-indicator message="Loading..." set-focus />; | ||
} | ||
if (representatives?.length) { | ||
return ( | ||
<> | ||
{representatives.map((rep, index) => { | ||
const representative = rep.data; | ||
return ( | ||
<SearchResult | ||
representativeName={ | ||
representative.attributes.fullName || | ||
representative.attributes.name | ||
} | ||
key={index} | ||
type={representative.type} | ||
addressLine1={representative.attributes.addressLine1} | ||
addressLine2={representative.attributes.addressLine2} | ||
addressLine3={representative.attributes.addressLine3} | ||
city={representative.attributes.city} | ||
stateCode={representative.attributes.stateCode} | ||
zipCode={representative.attributes.zipCode} | ||
phone={representative.attributes.phone} | ||
email={representative.attributes.email} | ||
representativeId={representative.id} | ||
/> | ||
); | ||
})} | ||
</> | ||
); | ||
} | ||
return null; | ||
}; | ||
|
||
return ( | ||
<> | ||
<va-card role="search"> | ||
<label | ||
htmlFor="representative-search" | ||
id="representative-search-label" | ||
className="vads-u-margin-top--0 vads-u-margin-bottom--1p5" | ||
> | ||
<VaTextInput | ||
id="representative_search" | ||
name="representative_search" | ||
error={error} | ||
onInput={handleChange} | ||
required | ||
/> | ||
<VaButton | ||
data-testid="representative-search-btn" | ||
text="Search" | ||
onClick={handleClick} | ||
/> | ||
</label> | ||
</va-card> | ||
|
||
{searchResults()} | ||
</> | ||
); | ||
}; | ||
|
||
SelectAccreditedRepresentative.propTypes = { | ||
fetchRepresentatives: PropTypes.func, | ||
}; | ||
|
||
export default SelectAccreditedRepresentative; |
Oops, something went wrong.