Finished incorporating map into tpa search page

This commit is contained in:
Michaela Brydon 2023-11-13 16:18:52 -05:00
parent c56a404704
commit d897fbbad1
9 changed files with 224 additions and 70 deletions

View file

@ -5,6 +5,13 @@
window.dataLayer = [{}];
</script>
<script><%= process.env.VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY %></script>
<!-- TODO move google maps script to environment variable -->
<script>
(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
key: "<%= process.env.VUE_APP_GOOGLE_PLACES_API_KEY %>",
v: "weekly",
});
</script>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">

View file

@ -136,7 +136,7 @@ const endpoints = Object.freeze({
method: 'GET'
},
GooglePlaces: {
url: 'https://maps.googleapis.com/maps/api/js?key={apiKey}&libraries=places'
url: (apiKey) => `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places`
},
ValidateClientTag: {
url: '/clientauth/api/v1/clientauth/validate-client-tag',

View file

@ -196,7 +196,7 @@ export default {
const addressField1 = document.getElementById('autocomplete');
const self = this;
const url = endpoints.GooglePlaces.url.replace('{apiKey}', applicationConfig.GOOGLE_PLACES_API_KEY);
const url = endpoints.GooglePlaces.url(applicationConfig.GOOGLE_PLACES_API_KEY);
this.$loadScript(url)
.then(() => {

View file

@ -0,0 +1,89 @@
<template>
<div>
<div id="map" />
</div>
</template>
<script>
export default {
name: 'google-map',
components: { },
props: {
addresses: Array,
zipCode: String
},
emits: [],
data() {
return {
country: 'USA',
geocoder: null
};
},
computed: {
zipCodeAddress() {
return `${this.zipCode} ${this.country}`;
}
},
watch: {
async addresses(newAddresses) {
this.createMapWithMarkersForAddresses(newAddresses);
}
},
methods: {
async getMap(center) {
const { Map } = await window.google.maps.importLibrary('maps');
return new Map(document.getElementById('map'), {
center,
mapId: 'map_id',
mapTypeControl: false
});
},
getBounds(locations) {
const bounds = new window.google.maps.LatLngBounds();
locations.forEach((location) => bounds.extend(location));
return bounds;
},
async setGeocoder() {
if (this.geocoder == null) {
const { Geocoder } = await window.google.maps.importLibrary('geocoding');
this.geocoder = new Geocoder();
}
},
async addMarkersToMap(map, positions) {
const { AdvancedMarkerElement } = await window.google.maps.importLibrary('marker');
positions.forEach((position) => new AdvancedMarkerElement({ map, position }));
},
async getLocationsFromAddresses(addresses) {
await this.setGeocoder();
const geocodeShopAddressPromises = addresses?.map((address) => this.geocoder.geocode({ address })) ?? [];
const geocodeShopAddressResults = await Promise.all(geocodeShopAddressPromises);
return geocodeShopAddressResults.map((result) => result.results[0].geometry.location);
},
async getBoundsFromAddress(address) {
await this.setGeocoder();
const result = await this.geocoder.geocode({ address });
return result.results[0].geometry.bounds;
},
async createMapWithMarkersForAddresses(addresses) {
const shopPositions = await this.getLocationsFromAddresses(addresses);
const zipBounds = await this.getBoundsFromAddress(this.zipCodeAddress);
const map = await this.getMap(zipBounds.getCenter());
await this.addMarkersToMap(map, shopPositions);
const positionsToDisplay = shopPositions.concat(zipBounds.getNorthEast(), zipBounds.getSouthWest());
const bounds = this.getBounds(positionsToDisplay);
map.fitBounds(bounds);
}
}
};
</script>
<style lang="scss" scoped>
#map {
height: 250px;
width: 100%;
}
</style>

View file

@ -17,7 +17,7 @@
</span>
<span
id="buttonLabelSubCopySpan"
class="m-0 caption ms-1"
class="m-0 caption ms-2"
:class="textPosition">{{ buttonLabelSubCopy }}
</span>
<div

View file

@ -512,7 +512,7 @@ describe('TPA search page', () => {
useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (newProviders));
// Act
const result = await wrapper.vm.getProviders();
const result = await wrapper.vm.getProviderButtonData();
// Assert
expect(result).toEqual([]);
@ -525,7 +525,7 @@ describe('TPA search page', () => {
useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (providers));
// Act
const result = await wrapper.vm.getProviders();
const result = await wrapper.vm.getProviderButtonData();
// Assert
expect(result).toEqual(providers.data);

View file

@ -36,18 +36,17 @@
</div>
</div>
</Form>
<googleMap
id="map"
class="mb-4"
:addresses="providerAddresses"
:zipCode="mapZipCode"></googleMap>
<Form
id="providerSelectionForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="container-fluid pb-2">
<p
id="map"
class="mb-4">
Placeholder for Map
</p>
<div class="mx-5">
<dropdownQuestion
id="searchRadiusFilter"
@ -65,7 +64,7 @@
buttonTypeString="shopListButton"
:buttonTypeObject="shopListButton"
class="radioQuestion"
:answers="providers"
:answers="providerButtonData"
groupName="chooseShop"
textPosition="text-start"
isRequired
@ -105,11 +104,12 @@ import { Form } from 'vee-validate';
import siteHeader from '@/iss-components/site-header/site-header.vue';
import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue';
import dropdownQuestion from '@/digital-components/dropdown-question/dropdown-question.vue';
import buttonQuestion from '@/digital-components/button-question/button-question.vue'
import shopListButton from '@/iss-components/shop-list-button/shop-list-button.vue'
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
import textLink from '@/ux-components/text-link/text-link.vue';
import alert from '@/ux-components/alert/alert.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import googleMap from '@/iss-components/google-map/google-map.vue';
import shopListButton from '@/iss-components/shop-list-button/shop-list-button.vue';
// Supporting files
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
@ -118,7 +118,6 @@ import { useMainStore } from '@/store';
import BaseFormMixin from '@/mixins/base-form-mixin.js';
import globalRules from '@/constants/global-rules.js';
import widgetFields from '@/constants/cms-widget-fields.js';
import routerParams from '@/router/router-constants/router-params';
export default {
name: 'tpa-search',
@ -129,6 +128,7 @@ export default {
buttonQuestion,
textLink,
alert,
googleMap,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form
@ -144,34 +144,16 @@ export default {
];
const resultMap = await settleAllPromises(promiseResultMap);
const radiusOptions = [25, 50, 100];
const { zipCode } = useMainStore().order.customer.address;
const tpaProvidersRadius25 = await useMainStore().getTpaProviders(zipCode, radiusOptions[0]);
const tpaProvidersRadius50 = await useMainStore().getTpaProviders(zipCode, radiusOptions[1]);
const tpaProvidersRadius100 = await useMainStore().getTpaProviders(zipCode, radiusOptions[2]);
let radius = '25 miles';
let providers = tpaProvidersRadius25;
if ((tpaProvidersRadius25?.data ?? []).length === 0) {
radius = '50 miles';
providers = tpaProvidersRadius50;
if ((tpaProvidersRadius50?.data ?? []).length === 0) {
radius = '100 miles';
providers = tpaProvidersRadius100;
}
}
next((vm) => {
next(async (vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.setFilter(radius);
vm.setProviders(providers);
await vm.setInitialFilterAndProviders();
});
},
data() {
const { zipCode } = useMainStore().order.customer.address;
return {
zipCode,
mapZipCode: zipCode,
filter: '',
providers: [],
selectedProviderNumber: '',
@ -242,24 +224,56 @@ export default {
this.widget.noNetworkShopsAlert,
widgetFields.ALERT_WIDGET.HEADLINE_TEXT
)?.replaceAll('{custom:radiusInMiles}', this.radiusInMiles);
},
providerAddresses() {
return this.providers?.map((provider) => this.getProviderAddress(provider)) ?? [];
},
providerButtonData() {
return this.providers?.map((provider) => this.getShopButtonDataFromProvider(provider)) ?? [];
}
},
watch: {
async filter() {
// TODO right now this results in getProviders being called once more than it needs to be
this.providers = await this.getProviders();
this.providers = await this.getProviderButtonData();
this.mapZipCode = this.zipCode;
},
providers(newProviders) {
this.selectedProviderNumber = newProviders?.length === 1 ?? false
? newProviders[0].value
? newProviders[0].providerNumber
: '';
}
},
methods:
{
async getProviders() {
getProviderAddress(provider) {
const addressLine2 = `${provider.address.city}, ${provider.address.state} ${provider.address.zipCode}`;
return `${provider.address.streetAddress}, ${addressLine2}`;
},
async setInitialFilterAndProviders() {
const radiusOptions = [25, 50, 100];
const { zipCode } = useMainStore().order.customer.address;
const tpaProvidersRadius25 = await useMainStore().getTpaProviders(zipCode, radiusOptions[0]);
const tpaProvidersRadius50 = await useMainStore().getTpaProviders(zipCode, radiusOptions[1]);
const tpaProvidersRadius100 = await useMainStore().getTpaProviders(zipCode, radiusOptions[2]);
let radius = '25 miles';
let providers = tpaProvidersRadius25;
if ((tpaProvidersRadius25?.data?.shopProviders ?? []).length === 0) {
radius = '50 miles';
providers = tpaProvidersRadius50;
if ((tpaProvidersRadius50?.data?.shopProviders ?? []).length === 0) {
radius = '100 miles';
providers = tpaProvidersRadius100;
}
}
this.filter = radius;
this.providers = providers.data.shopProviders;
},
async getProviderButtonData() {
const getTpaProvidersResult = await useMainStore().getTpaProviders(this.zipCode, this.radiusInMiles);
return getTpaProvidersResult?.data ?? [];
return getTpaProvidersResult?.data?.shopProviders ?? [];
},
doNotSeeMyShopLinkClick() {
this.$router.navigate(
@ -268,7 +282,8 @@ export default {
);
},
async searchClick() {
this.providers = await this.getProviders();
this.providers = await this.getProviderButtonData();
this.mapZipCode = this.zipCode;
},
backButtonAction() {
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
@ -287,11 +302,16 @@ export default {
return null;
}
},
setFilter(filter) {
this.filter = filter;
},
setProviders(providers) {
this.providers = providers;
getShopButtonDataFromProvider(provider) {
const { phoneNumber } = provider;
const distance = +provider.distanceInMiles.toFixed(1);
return {
buttonLabel: provider.name,
buttonLabelSubCopy: `${distance} mi`,
buttonBodyCopy: `${this.getProviderAddress(provider)}<br>${phoneNumber}`,
value: provider.providerNumber
};
}
}
};

View file

@ -806,38 +806,76 @@ export const useMainStore = defineStore({
});
},
// TODO what info is returned here
// TODO update with real endpoint
getTpaProviders(zipCode, radius) {
return new Promise((resolve, reject) => {
if (radius === 25) {
resolve({});
resolve({
data: {
mobileProviderNumber: null,
shopProviders: null
}
});
} else if (radius === 50) {
resolve({
data: [
data: {
mobileProviderNumber: null,
shopProviders: [
{
buttonLabel: 'USA Auto Glass',
buttonLabelSubCopy: '1.5 mi',
buttonBodyCopy: '760 Dearborn Park Ln, Worthington, OH 43085<br>614-123-5555',
value: '000123'
name: 'USA Auto Glass',
address: {
city: 'WESTERVILLE',
country: 'US',
state: 'OH',
streetAddress: '4403 EXECUTIVE PKWY',
streetAddress2: '',
zipCode: '43081',
zipCodeCtu: '01820'
},
distanceInMiles: 8.393453111956896,
providerNumber: '003335',
phoneNumber: '614-123-5555'
}
]
}
});
} else {
resolve({
data: [
data: {
mobileProviderNumber: null,
shopProviders: [
{
buttonLabel: 'USA Auto Glass',
buttonLabelSubCopy: '1.5 mi',
buttonBodyCopy: '760 Dearborn Park Ln, Worthington, OH 43085<br>614-123-5555',
value: '000123'
name: 'USA Auto Glass',
address: {
city: 'WESTERVILLE',
country: 'US',
state: 'OH',
streetAddress: '4403 EXECUTIVE PKWY',
streetAddress2: '',
zipCode: '43081',
zipCodeCtu: '01820'
},
distanceInMiles: 8.393453111956896,
providerNumber: '003335',
phoneNumber: '614-123-5555'
},
{
buttonLabel: 'USA Auto Glass',
buttonLabelSubCopy: '1.5 mi',
buttonBodyCopy: '760 Dearborn Park Ln, Worthington, OH 43085<br>614-123-5555',
value: '000123'
name: 'Safelite AutoGlass',
address: {
city: 'WORTHINGTON',
country: 'US',
state: 'OH',
streetAddress: '760 DEARBORN PARK LN',
streetAddress2: '',
zipCode: '43085',
zipCodeCtu: '01820'
},
distanceInMiles: 8.704336770196678,
providerNumber: '001820',
phoneNumber: '740-555-1234'
}
]
}
});
}
});