<div data-react="find-installer" data-props="{"placesEndpoint":"/mocks/api/findInstaller.json","lang":{"startTitle":"Hitta installatör","startDescription":"Behöver du hjälp att installera? Eller är du kanske osäker på vilka produkter och lösningar som är bäst för dina behov i hemmet? Tala med en erfaren auktoriserad installatör så får du hjälp att skapa inomhusklimat i världsklass i ditt eget hem."}}" class="find-installer"></div>
<div data-react="find-installer" data-props="{{jsonEncode props}}" class="find-installer"></div>
{
"props": {
"placesEndpoint": "/mocks/api/findInstaller.json",
"lang": {
"startTitle": "Hitta installatör",
"startDescription": "Behöver du hjälp att installera? Eller är du kanske osäker på vilka produkter och lösningar som är bäst för dina behov i hemmet? Tala med en erfaren auktoriserad installatör så får du hjälp att skapa inomhusklimat i världsklass i ditt eget hem."
}
}
}
import React from 'react';
import PropTypes from 'prop-types';
import StartView from './StartView';
import MapView from './MapView';
const FindInstaller = (props) => {
const [selectedLocationCords, setSelectedLocationCords] = React.useState();
const onSelect = (cords) => setSelectedLocationCords(() => cords);
return (
<>
{selectedLocationCords
? <MapView
selectedLocationCords={selectedLocationCords}
placesEndpoint={props.placesEndpoint}
/>
: <StartView
lang={props.lang}
onSelect={onSelect}
/>
}
</>
);
};
FindInstaller.propTypes = {
placesEndpoint: PropTypes.string.isRequired
};
export default FindInstaller;
import React from 'react';
import PropTypes from 'prop-types';
import GoogleMap from '../google-map/GoogleMap';
import SearchFieldPlaces from './SearchFieldPlaces';
import MapContactCard from '../map-contact-card/MapContactCard';
import { installersToContactInfoProps } from './utils/parsers';
const MapView = (props) => {
const [selectedLocationCords, setSelectedLocationCords] = React.useState(props.selectedLocationCords);
const [places, setPlaces] = React.useState();
const [visiblePlaces, setVisiblePlaces] = React.useState([]);
const [selectedPlace, setSelectedPlace] = React.useState('');
const mapRef = React.useRef();
const openInfoWindow = (id) => {
mapRef.current.openInfoWindow(id);
};
React.useEffect(() => {
if (mapRef.current) {
openInfoWindow(selectedPlace);
}
}, [selectedPlace, mapRef]);
React.useEffect(() => {
fetch(props.placesEndpoint)
.then((res) => res.json())
.then((data) => setPlaces(data.Result));
}, []);
return (
<div className="find-installer-map">
<aside className="find-installer-map__result">
<div className="row">
<div className="col col--span-12">
<div className="find-installer-map__search">
<SearchFieldPlaces
onSelect={(cords) => setSelectedLocationCords(cords)}
/>
</div>
</div>
</div>
<div className="find-installer-map__cards">
<div className="row">
{places
? installersToContactInfoProps(places, selectedLocationCords).map(({ contactInfo, key, title }) => (
visiblePlaces.includes(key) &&
<div className="col col--span-12 col--span-m-6 col--span-xl-12" key={key}>
<MapContactCard
key={key}
id={String(key)}
title={title}
contactInfo={contactInfo}
active={selectedPlace === key}
onClick={() => setSelectedPlace(key)}
/>
</div>
))
// ToDo: Add loading state
: null}
</div>
</div>
</aside>
<div className="find-installer-map__map">
{places
? <GoogleMap
ref={mapRef}
places={places}
targetPlaceCords={selectedLocationCords}
onVisibleMarkersUpdate={setVisiblePlaces}
onInfoWindowClose={() => setSelectedPlace(() => '')}
onMarkerClick={(marker) => setSelectedPlace(() => marker.Id)}
/>
// ToDo: Add loading state
: null}
</div>
</div>
);
};
MapView.propTypes = {
selectedLocationCords: PropTypes.object.isRequired,
setSearchLatLon: PropTypes.shape({
lat: PropTypes.number,
lon: PropTypes.number
}),
placesEndpoint: PropTypes.string.isRequired
};
export default MapView;
import React from 'react';
import PropTypes from 'prop-types';
import { GoogleMapsContext } from '../../context/GoogleMapsProvider';
import SearchField, { Geolocation } from '../search-field';
const ItemComponent = (props) => (
<>
<svg className="search-field-places__icon-wrapper" focusable="false">
<use xlinkHref="#icon-map-pin"/>
</svg>
{props.description}
</>
);
const SearchFieldPlaces = (props) => {
const { autocompleteService, googleMapsApi } = React.useContext(GoogleMapsContext);
const [searchResult, setSearchResult] = React.useState([]);
const updateSearchResult = (e) => {
const input = e.target.value || '';
if (input.length > 0) {
autocompleteService.getPlacePredictions({ input }, (places) => {
setSearchResult(() => (
places
? places.map((place) => ({
...place,
value: place.description,
id: place.place_id
}))
: []
));
});
} else {
// Reset statae when there's no search result
setSearchResult(() => []);
}
};
const getCords = async (e) => {
const service = new googleMapsApi.places.PlacesService(document.createElement('div'));
const cords = await new Promise((resolve, reject) => {
try {
service.getDetails({ placeId: e.place_id }, (data) => {
resolve(data.geometry.location);
});
} catch (e) {
reject(e);
}
});
props.onSelect(cords);
};
const onGeolocation = (latLng) => {
props.onSelect(new googleMapsApi.LatLng(...latLng));
};
return (
<>
{googleMapsApi &&
<SearchField
onChange={getCords}
items={searchResult}
onInputChange={updateSearchResult}
itemStyle="search-field-places__result"
className={props.className}
itemComponent={ItemComponent}
>
<Geolocation onGeolocation={onGeolocation} />
</SearchField>
}
</>
);
};
SearchFieldPlaces.propTypes = {
onChange: PropTypes.func
};
SearchFieldPlaces.defaultProps = {
onChange: () => {}
};
export default SearchFieldPlaces;
import React from 'react';
import PropTypes from 'prop-types';
import SearchFieldPlaces from './SearchFieldPlaces';
const StartView = (props) => {
return (
<div className="find-installer-start">
<div className="find-installer-start__panel">
<h1>{props.lang.startTitle}</h1>
<p className="preamble">
{props.lang.startDescription}
</p>
<SearchFieldPlaces
onSelect={props.onSelect}
className="search-field--big-m h-no-margin-bottom"
/>
</div>
</div>
);
};
StartView.propTypes = {
onSelect: PropTypes.func
};
StartView.defaultProps = {
onSelect: () => {}
};
export default StartView;
.find-installer {
display: flex;
width: 100%;
align-items: center;
justify-content: space-around;
@include breakpoint($xl) {
height: 70vh;
min-height: size(60);
}
}
// start view
.find-installer-start {
display: flex;
align-items: center;
justify-content: space-around;
width: 100%;
height: 100%;
background: url("./img/start-background.png") center no-repeat;
background-size: cover;
}
.find-installer-start__panel {
width: 100%;
max-width: size(116);
background: $color-white;
padding: size(4) size(2);
margin: size(5) size(1);
@include breakpoint($m) {
padding: size(10) size(15);
margin: size(6) size(2);
}
}
// map view
.find-installer-map {
display: flex;
flex: 1;
height: 100%;
flex-direction: column;
flex-direction: column-reverse;
@include breakpoint($xl) {
flex-direction: row;
flex-direction: initial;
}
}
.find-installer-map__map {
flex: 1 0 auto;
height: 50vh;
@include breakpoint($xl) {
display: flex;
flex-direction: column;
height: 100%;
width: size(52);
padding: 0;
}
}
// sidebar
.find-installer-map__result {
background: $color-gray-1;
width: 100%;
padding-top: size(2);
@include breakpoint($xl) {
display: flex;
flex-direction: column;
width: size(52);
padding-top: size(3);
}
}
.find-installer-map__search {
margin-bottom: size(-2);
@include breakpoint($xl) {
margin-bottom: size(-1);
}
}
.find-installer-map__cards {
flex: 1;
overflow: auto;
}
.search-field-places__result {
min-height: size(8);
padding: size(2);
}
.search-field-places__icon-wrapper {
width: size(4);
height: size(4);
margin-right: size(2);
color: $color-green;
flex-shrink: 0;
}
No notes defined.