-
-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Port map to React #62
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
c155fc6
Map in flight
snyderc de93d24
Fixed merge conflict by accepting both
snyderc 829a591
Functioning map
snyderc d27c5bf
Finished map popup
snyderc 7e66ab2
Parse booking urls on homepage and search page
snyderc 957b6e8
Fix merge conflicts
snyderc 331dd49
Eslint
snyderc 5ef815d
Revert "Eslint"
snyderc d3f0c03
Lint
snyderc c13d81e
Changed indent and added function comment
snyderc c978b4a
Linting
snyderc 8ed9d86
Constants and green star update
snyderc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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,194 @@ | ||
import React, { useState, useEffect } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import GoogleMapReact from 'google-map-react'; | ||
import parseBookAppointmentString from './utilities/parseBookAppointmentString'; | ||
|
||
// High volume, large venue sites | ||
const MASS_VACCINATION_SITES = [ | ||
'Foxborough: Gillette Stadium', | ||
'Boston: Fenway Park', | ||
'Danvers: Doubletree Hotel', | ||
'Springfield: Eastfield Mall', | ||
'Dartmouth: Former Circuit City', | ||
'Lowell: Lowell General Hospital at Cross River Center', | ||
'Natick: Natick Mall' | ||
]; | ||
|
||
const ELIGIBLE_PEOPLE_STATEWIDE_TEXT = [ | ||
'All eligible people statewide', | ||
'Eligible populations statewide' | ||
]; | ||
|
||
const doesSiteServeAllEligiblePeopleStatewide = serves => ELIGIBLE_PEOPLE_STATEWIDE_TEXT.includes(serves?.trim()); | ||
|
||
const isSiteAMassVaccinationSite = locationName => MASS_VACCINATION_SITES.includes(locationName); | ||
|
||
const parseDate = dateString => ( | ||
new Date(Date.parse(dateString)).toLocaleString('en-US', {timeZone: 'America/New_York'}) | ||
); | ||
|
||
const parseLocationData = data => { | ||
return data.map( site => ( | ||
{ | ||
id: site.id, | ||
locationName: site.fields['Location Name'] ?? '', | ||
address: site.fields['Full Address'] ?? '', | ||
populationsServed: site.fields['Serves'] ?? '', | ||
vaccineAvailability: site.fields['Availability'] ?? '', | ||
lastUpdated: (site.fields['Last Updated'] && parseDate(site.fields['Last Updated'])) ?? '', | ||
bookAppointmentInformation: (site.fields['Book an appointment'] && parseBookAppointmentString(site.fields['Book an appointment'])) ?? '', | ||
latitude: site.fields['Latitude'] ?? 0, | ||
longitude: site.fields['Longitude'] ?? 0, | ||
sitePinShape: determineSitePinShape( | ||
site.fields['Availability'] ?? '', | ||
site.fields['Serves'] ?? '', | ||
site.fields['Location Name'] ?? '' | ||
) | ||
} | ||
)); | ||
}; | ||
|
||
const determineSitePinShape = (availability, serves, locationName) => { | ||
snyderc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!availability || availability?.trim() === 'None') { | ||
return 'dot'; | ||
} else if (doesSiteServeAllEligiblePeopleStatewide(serves)) { | ||
return 'star star-green'; | ||
} else if (isSiteAMassVaccinationSite(locationName)) { | ||
return 'star star-red'; | ||
} else { | ||
return 'star star-blue'; | ||
} | ||
}; | ||
|
||
// google-map-react allows you to pass a "$hover" destructured prop if you want to have an effect on hover | ||
snyderc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const Marker = ({ id, lat, lng, sitePinShape, setPopupData, getSiteDataByKey }) => { | ||
const handleClick = () => { | ||
const data = getSiteDataByKey(id); | ||
setPopupData({ | ||
lat, | ||
lng, | ||
data | ||
}); | ||
}; | ||
return ( | ||
<div | ||
className={sitePinShape} | ||
onClick={handleClick} | ||
style={{ | ||
position: 'absolute', | ||
transform: 'translate(-50%, -50%)', | ||
cursor: 'pointer' | ||
}} | ||
> | ||
</div> | ||
); | ||
}; | ||
|
||
Marker.propTypes = { | ||
id: PropTypes.string, | ||
lat: PropTypes.number, | ||
lng: PropTypes.number, | ||
sitePinShape: PropTypes.string, | ||
setPopupData: PropTypes.func, | ||
getSiteDataByKey: PropTypes.func, | ||
}; | ||
|
||
const Popup = ({data, setPopupData}) => ( | ||
snyderc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<div style={{ | ||
position: 'absolute', | ||
transform: 'translate(0%, -50%)', | ||
border: '1px solid black', | ||
color: '#000000', | ||
backgroundColor: '#FFFFFF', | ||
width: '300px', | ||
borderRadius: '5px', | ||
boxShadow: '5px 5px', | ||
padding: '5px' | ||
}}> | ||
<div id="content"> | ||
<h4 id="firstHeading" className="firstHeading">{data.locationName}</h4> | ||
<div id="bodyContent"> | ||
<p><b>Details</b> {data.populationsServed}</p> | ||
<p><b>Address</b> {data.address}</p> | ||
<p><b>Availability</b> {data.vaccineAvailability}</p> | ||
<p>(Availability last updated {data.lastUpdated})</p> | ||
<p><b>Book now</b> {data.bookAppointmentInformation}</p> | ||
<button onClick={() => setPopupData({})}>Close</button> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
|
||
Popup.propTypes = { | ||
data: PropTypes.shape( | ||
{ | ||
locationName: PropTypes.string, | ||
populationsServed: PropTypes.string, | ||
address: PropTypes.string, | ||
vaccineAvailability: PropTypes.string, | ||
lastUpdated: PropTypes.string, | ||
bookAppointmentInformation: PropTypes.array, | ||
} | ||
), | ||
setPopupData: PropTypes.func, | ||
}; | ||
|
||
const Map = ({height = '400px', width = '100%'}) => { | ||
const [siteData, setSiteData] = useState([]); | ||
const [popupData, setPopupData] = useState({}); | ||
|
||
const bostonCoordinates = { | ||
lat: 42.360081, | ||
lng: -71.058884 | ||
}; | ||
|
||
const defaultMassachusettsZoom = 8; | ||
|
||
const getSiteDataByKey = key => siteData.find(site => { | ||
return key === site.id; | ||
}); | ||
|
||
useEffect(() => { | ||
fetch('/initmap') | ||
.then(response => response.json()) | ||
.then(siteData => parseLocationData(siteData)) | ||
.then(siteData => setSiteData(siteData)); | ||
}); | ||
|
||
return ( | ||
// Container element must have height and width for map to display. See https://developers.google.com/maps/documentation/javascript/overview#Map_DOM_Elements | ||
<div style={{ height, width }}> | ||
<GoogleMapReact | ||
bootstrapURLKeys={{ key: 'AIzaSyDxF3aT2MwmgzcAzFt5PtB-B3UNp4Js2h4' }} | ||
defaultCenter={bostonCoordinates} | ||
defaultZoom={defaultMassachusettsZoom} | ||
draggableCursor="crosshair" | ||
> | ||
{siteData && siteData.map((site) => ( | ||
<Marker | ||
key={site.id} | ||
id={site.id} | ||
lat={site.latitude} | ||
lng={site.longitude} | ||
sitePinShape={site.sitePinShape} | ||
setPopupData={setPopupData} | ||
getSiteDataByKey={getSiteDataByKey} | ||
/> | ||
))} | ||
{popupData.data && (<Popup | ||
lat={popupData.lat} | ||
lng={popupData.lng} | ||
data={popupData.data} | ||
setPopupData={setPopupData} | ||
/>)} | ||
</GoogleMapReact> | ||
</div> | ||
); | ||
}; | ||
|
||
Map.propTypes = { | ||
height: PropTypes.string, | ||
width: PropTypes.string, | ||
}; | ||
|
||
export default Map; |
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,35 @@ | ||
import React from 'react'; | ||
import processString from 'react-process-string'; | ||
|
||
const firstParser = (key, result) => ( | ||
<span key={key}> | ||
<a target="_blank" rel="noreferrer" href={`${result[1]}://${result[2]}.${result[3]}${result[4]}`}>{result[2]}.{result[3]}{result[4]}</a>{result[5]} | ||
</span> | ||
); | ||
|
||
const secondParser = (key, result) => ( | ||
<span key={key}> | ||
<a target="_blank" rel="noreferrer" href={`http://${result[1]}.${result[2]}${result[3]}`}>{result[1]}.{result[2]}{result[3]}</a>{result[4]} | ||
</span> | ||
); | ||
|
||
/** | ||
* Given a string that contains URLs, returns an array that turns the URLs into hyperlinks | ||
* so when you use the output of this function in a React layout, the URLs will be clickable | ||
* | ||
* Based on example code/regex from https://www.npmjs.com/package/react-process-string | ||
* | ||
* @return array | ||
*/ | ||
const parseBookAppointmentString = text => { | ||
let config = [{ | ||
regex: /(http|https):\/\/(\S+)\.([a-z]{2,}?)(.*?)( |,|$|\.)/gim, | ||
fn: firstParser | ||
}, { | ||
regex: /(\S+)\.([a-z]{2,}?)(.*?)( |,|$|\.)/gim, | ||
fn: secondParser | ||
}]; | ||
return processString(config)(text); | ||
}; | ||
|
||
export default parseBookAppointmentString; | ||
snyderc marked this conversation as resolved.
Show resolved
Hide resolved
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
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 |
---|---|---|
@@ -1,11 +1,25 @@ | ||
import React from 'react'; | ||
|
||
import Layout from '../../components/Layout'; | ||
import Map from '../../components/Map'; | ||
|
||
const Home = () => ( | ||
<Layout pageTitle="Home"> | ||
<div> | ||
Home page | ||
<div className="jumbotron"> | ||
<h2>COVID-19 Vaccine Availability</h2> | ||
<p className="lead">For <a href="/eligibility"> eligible individuals </a>, find an appointment from the map below or</p> | ||
<div className="btn-group" style={{width: '100%', display:'flex', alignItems: 'center', justifyContent: 'center'}}> | ||
<a className="btn btn-success" data-toggle="modal" href="/search" style={{justifyContent: 'center'}}>Find Locations Near You</a> | ||
</div> | ||
|
||
</div> | ||
<Map /> | ||
<p> <b> Red star: </b> Mass Vaccination Sites (high volume, large venue sites) </p> | ||
<p> <b> Green star: </b> General Vaccination Sites (healthcare locations) </p> | ||
<p> <b> Blue star: </b> Local Vaccination Site (open to select cities/towns) </p> | ||
<p> <b> Gray dot: </b> No availability currently </p> | ||
<p> Seeking volunteers, reach out to <a href="mailto:[email protected]"> [email protected] </a> to help out.</p> | ||
</div> | ||
</Layout> | ||
); | ||
|
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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is to fix an error TypeError: Cannot read property 'range' of null
Occurred while linting /Users/snyderc/Documents/vaccinatema/components/utilities/parseBookAppointmentString.js:1
babel/babel-eslint#681