Merge pull request #1003 from Safelite/feature/humphries/INSR-7768
INSR-7768: service-location parity updates
This commit is contained in:
commit
f94d3a0bbd
37 changed files with 1482 additions and 1085 deletions
3
src/assets/img/icons/alert-circle.svg
Normal file
3
src/assets/img/icons/alert-circle.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="21" viewBox="0 0 20 21">
|
||||||
|
<path fill="#E86421" fill-rule="nonzero" d="M10 .5c5.523 0 10 4.477 10 10s-4.477 10-10 10-10-4.477-10-10S4.477.5 10 .5zm.043 13.6a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm.77-9.2h-1.54l-.088.009a.502.502 0 0 0-.299.193.66.66 0 0 0-.125.47l.747 6.806.017.096c.065.249.263.425.495.426l.084-.008c.22-.042.396-.246.427-.51l.793-6.805.004-.102a.651.651 0 0 0-.127-.37.489.489 0 0 0-.388-.205z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 474 B |
3
src/assets/img/icons/chevron-up.svg
Normal file
3
src/assets/img/icons/chevron-up.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="9" viewBox="0 0 15 9">
|
||||||
|
<path fill="#E86421" fill-rule="nonzero" d="M7.5 0a.806.806 0 0 0-.593.265L.246 7.455a.957.957 0 0 0 0 1.28.796.796 0 0 0 1.185 0l6.07-6.55 6.068 6.55a.796.796 0 0 0 1.186 0 .957.957 0 0 0 0-1.28L8.093.265A.806.806 0 0 0 7.5 0"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 323 B |
|
|
@ -1,9 +0,0 @@
|
||||||
const buttonVariants = Object.freeze({
|
|
||||||
primary: 'primary',
|
|
||||||
secondary: 'secondary',
|
|
||||||
success: 'success',
|
|
||||||
link: 'link',
|
|
||||||
navigation: 'navigation'
|
|
||||||
});
|
|
||||||
|
|
||||||
export default buttonVariants;
|
|
||||||
15
src/constants/component-variants.js
Normal file
15
src/constants/component-variants.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
export const buttonVariants = Object.freeze({
|
||||||
|
primary: 'primary',
|
||||||
|
secondary: 'secondary',
|
||||||
|
success: 'success',
|
||||||
|
link: 'link',
|
||||||
|
navigation: 'navigation'
|
||||||
|
});
|
||||||
|
export const dropdownVariants = Object.freeze({
|
||||||
|
primary: 'primary',
|
||||||
|
compact: 'compact'
|
||||||
|
});
|
||||||
|
export const modalPositions = Object.freeze({
|
||||||
|
center: 'center',
|
||||||
|
edge: 'edge'
|
||||||
|
});
|
||||||
|
|
@ -23,8 +23,13 @@ const errorMessages = Object.freeze({
|
||||||
LAST_NAME_REQUIRED: 'Please enter your last name',
|
LAST_NAME_REQUIRED: 'Please enter your last name',
|
||||||
EMAIL_ADDRESS_REQUIRED: 'Email is required.',
|
EMAIL_ADDRESS_REQUIRED: 'Email is required.',
|
||||||
EMAIL_ADDRESS_FORMAT: 'Please enter a valid email address.',
|
EMAIL_ADDRESS_FORMAT: 'Please enter a valid email address.',
|
||||||
SERVICE_ZIP_REQUIRED: 'Please enter your service ZIP',
|
SERVICE_ZIP_REQUIRED: 'ZIP code is required',
|
||||||
SERVICE_ZIP_FORMAT: 'Please enter a valid service ZIP',
|
SERVICE_ZIP_FORMAT: 'Invalid ZIP, please enter a new ZIP (5 characters)',
|
||||||
|
MOBILE_SERVICE_ZIP_FORMAT: 'Invalid ZIP code',
|
||||||
|
NO_SERVICE_IN_AREA: (area) => `We're sorry, but we don't offer service in ${area} at this time.`,
|
||||||
|
PROVIDER_REQUIRED: 'Please select a provider.',
|
||||||
|
INVALID_ZIP: 'The ZIP you entered was invalid. Please enter a valid ZIP.',
|
||||||
|
ZIP_CODE_NOT_SERVICED_FOR_VEHICLE: 'We do not currently offer glass service for your vehicle in this ZIP code. Please try another ZIP code.',
|
||||||
VIN_REQUIRED: 'Please enter your VIN',
|
VIN_REQUIRED: 'Please enter your VIN',
|
||||||
VIN_FORMAT:
|
VIN_FORMAT:
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
|
|
|
||||||
|
|
@ -294,6 +294,11 @@ export default {
|
||||||
this.resetField({
|
this.resetField({
|
||||||
value: newValue
|
value: newValue
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
answers() {
|
||||||
|
this.resetField({
|
||||||
|
value: this.modelValue
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@
|
||||||
<div
|
<div
|
||||||
class="dropdown-question"
|
class="dropdown-question"
|
||||||
:class="{
|
:class="{
|
||||||
'has-error' : (meta.touched && errors && errors.length && !isDisabled) || hasError,
|
'has-error' : (meta?.touched && errors && errors.length && !isDisabled) || hasError,
|
||||||
'has-success': (meta.touched && (!errors || !errors.length) && !isDisabled)
|
'has-success': (meta?.touched && (!errors || !errors.length) && !isDisabled),
|
||||||
|
[`dropdown-${variant}`]: true
|
||||||
}">
|
}">
|
||||||
<label
|
<label
|
||||||
:for="inputId"
|
:for="inputId"
|
||||||
|
|
@ -34,7 +35,7 @@
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<div
|
<div
|
||||||
v-show="meta.touched && errorMessage && !isDisabled"
|
v-show="meta?.touched && errorMessage && !isDisabled"
|
||||||
class="row mt-1 form-test-error">
|
class="row mt-1 form-test-error">
|
||||||
<span role="alert">{{ errorMessage }}</span>
|
<span role="alert">{{ errorMessage }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -42,6 +43,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { dropdownVariants } from '@/constants/component-variants';
|
||||||
import { useField } from 'vee-validate';
|
import { useField } from 'vee-validate';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -59,7 +61,11 @@ export default {
|
||||||
validationRules: String,
|
validationRules: String,
|
||||||
cmsWidgetName: String,
|
cmsWidgetName: String,
|
||||||
hasError: Boolean,
|
hasError: Boolean,
|
||||||
placeHolderText: String
|
placeHolderText: String,
|
||||||
|
variant: {
|
||||||
|
type: String,
|
||||||
|
default: dropdownVariants.primary
|
||||||
|
}
|
||||||
},
|
},
|
||||||
emits: ['update:modelValue'],
|
emits: ['update:modelValue'],
|
||||||
setup(props) {
|
setup(props) {
|
||||||
|
|
@ -154,5 +160,30 @@ export default {
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.dropdown-compact {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
.form-label {
|
||||||
|
color: $darker-gray;
|
||||||
|
font-weight: $font-weight-light;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.form-select {
|
||||||
|
color: $heritage-blue-secondary;
|
||||||
|
background-position-x: 98%;
|
||||||
|
padding: 0;
|
||||||
|
padding-left: .3125rem;
|
||||||
|
width: 5.625rem;
|
||||||
|
min-height: auto;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
&:focus,
|
||||||
|
&:focus-visible {
|
||||||
|
border: .125rem solid $black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -13,19 +13,19 @@
|
||||||
}">
|
}">
|
||||||
<div class="modal-dialog"
|
<div class="modal-dialog"
|
||||||
:class="{
|
:class="{
|
||||||
'modal-dialog-edge': modalPosition === 'edge',
|
'modal-dialog-edge': modalPosition === modalPositions.edge,
|
||||||
'modal-dialog-centered': modalPosition === 'center'
|
'modal-dialog-centered': modalPosition === modalPositions.center
|
||||||
}">
|
}">
|
||||||
<div
|
<div
|
||||||
class="modal-content"
|
class="modal-content"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true">
|
aria-modal="true">
|
||||||
<div class="modal-header mb-2 mt-2">
|
<div class="modal-header mb-2 mt-2">
|
||||||
<label
|
<span
|
||||||
v-if="headerText"
|
v-if="headerText"
|
||||||
class="modal-title d-flex justify-content-center pb-0 w-100">
|
class="modal-title d-flex justify-content-center pb-0 w-100"
|
||||||
{{ headerText }}
|
:innerHTML="headerText">
|
||||||
</label>
|
</span>
|
||||||
<button
|
<button
|
||||||
ref="closeButton"
|
ref="closeButton"
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -60,6 +60,7 @@
|
||||||
import ButtonMain from '@/ux-components/button-main/button-main.vue';
|
import ButtonMain from '@/ux-components/button-main/button-main.vue';
|
||||||
import { Modal } from 'bootstrap';
|
import { Modal } from 'bootstrap';
|
||||||
import { useForm } from 'vee-validate';
|
import { useForm } from 'vee-validate';
|
||||||
|
import { modalPositions } from '@/constants/component-variants';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// eslint-disable-next-line vue/multi-word-component-names
|
// eslint-disable-next-line vue/multi-word-component-names
|
||||||
|
|
@ -84,7 +85,11 @@ export default {
|
||||||
},
|
},
|
||||||
modalPosition: {
|
modalPosition: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'edge'
|
default: modalPositions.edge
|
||||||
|
},
|
||||||
|
allowInvalidSubmit: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['footer-button-event', 'isModalOpened'],
|
emits: ['footer-button-event', 'isModalOpened'],
|
||||||
|
|
@ -102,7 +107,8 @@ export default {
|
||||||
modalId,
|
modalId,
|
||||||
meta,
|
meta,
|
||||||
validate,
|
validate,
|
||||||
resetForm
|
resetForm,
|
||||||
|
modalPositions
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -121,7 +127,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
async validateAndEmit() {
|
async validateAndEmit() {
|
||||||
const validationResult = await this.validate();
|
const validationResult = await this.validate();
|
||||||
if (validationResult.valid) {
|
if (validationResult.valid || this.allowInvalidSubmit) {
|
||||||
this.$emit('footer-button-event');
|
this.$emit('footer-button-event');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -257,6 +263,9 @@ export default {
|
||||||
border-radius: 1.5rem;
|
border-radius: 1.5rem;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
.modal-header {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.show .modal-dialog {
|
&.show .modal-dialog {
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
customText: String, // used to allow the insert of token values into textblock
|
customText: String, // used to allow the insert of token values into textblock
|
||||||
justifyText: String, // left, right, center
|
justifyText: String, // left, right, center
|
||||||
typeStyle: String, // h1-h6, body, small, label, caption
|
typeStyle: String, // h1-h6, body, small, label, caption, disclaimer
|
||||||
// (see Figma or Confluence documentation)
|
// (see Figma or Confluence documentation)
|
||||||
fontWeight: String, // bold=500, default is 400
|
fontWeight: String, // bold=500, default is 400
|
||||||
cmsWidgetName: String,
|
cmsWidgetName: String,
|
||||||
|
|
@ -109,5 +109,12 @@ export default {
|
||||||
&.dark {
|
&.dark {
|
||||||
color: $black;
|
color: $black;
|
||||||
}
|
}
|
||||||
|
&.disclaimer {
|
||||||
|
color: $lighter-gray;
|
||||||
|
font-weight: $font-weight-light;
|
||||||
|
font-size: .8125rem;
|
||||||
|
font-style: italic;
|
||||||
|
margin-top: .625rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@
|
||||||
class="input-wrapper"
|
class="input-wrapper"
|
||||||
:class="[
|
:class="[
|
||||||
includeSearchIcon ? 'has-search-icon' : '',
|
includeSearchIcon ? 'has-search-icon' : '',
|
||||||
includeSelectIcon ? 'has-select-icon' : ''
|
includeSelectIcon ? 'has-select-icon' : '',
|
||||||
|
hasTextButton ? 'has-text-button' : ''
|
||||||
]">
|
]">
|
||||||
<input
|
<input
|
||||||
:id="inputId"
|
:id="inputId"
|
||||||
|
|
@ -61,6 +62,13 @@
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
:data-bs-target="'#' + cmsWidgetName"
|
:data-bs-target="'#' + cmsWidgetName"
|
||||||
aria-label="Select button" />
|
aria-label="Select button" />
|
||||||
|
<buttonMain
|
||||||
|
v-if="hasTextButton"
|
||||||
|
ref="buttonMain"
|
||||||
|
variant="primary"
|
||||||
|
:buttonText="buttonText"
|
||||||
|
@clickEvent="clickedTextButton">
|
||||||
|
</buttonMain>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-show="errorMessage"
|
v-show="errorMessage"
|
||||||
|
|
@ -76,9 +84,13 @@
|
||||||
<script>
|
<script>
|
||||||
import { useField, validate } from 'vee-validate';
|
import { useField, validate } from 'vee-validate';
|
||||||
import { extractSpanText } from '@/helpers/text-helper';
|
import { extractSpanText } from '@/helpers/text-helper';
|
||||||
|
import buttonMain from '@/ux-components/button-main/button-main.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'textbox-question',
|
name: 'textbox-question',
|
||||||
|
components: {
|
||||||
|
buttonMain
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
@ -118,7 +130,11 @@ export default {
|
||||||
includeSelectIcon: Boolean,
|
includeSelectIcon: Boolean,
|
||||||
min: String,
|
min: String,
|
||||||
max: String,
|
max: String,
|
||||||
disableAutoFill: Boolean
|
disableAutoFill: Boolean,
|
||||||
|
buttonCallback: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
emits: ['focus', 'update:modelValue', 'textboxQuestionEvent.inputIdAssigned', 'click-event'],
|
emits: ['focus', 'update:modelValue', 'textboxQuestionEvent.inputIdAssigned', 'click-event'],
|
||||||
setup(props) {
|
setup(props) {
|
||||||
|
|
@ -161,6 +177,12 @@ export default {
|
||||||
questionTextForAria() {
|
questionTextForAria() {
|
||||||
return extractSpanText(this.getCmsContent(this.cmsWidgetName, 'QuestionText'));
|
return extractSpanText(this.getCmsContent(this.cmsWidgetName, 'QuestionText'));
|
||||||
},
|
},
|
||||||
|
buttonText() {
|
||||||
|
return this.getCmsContent(this.cmsWidgetName, 'ButtonText');
|
||||||
|
},
|
||||||
|
hasTextButton() {
|
||||||
|
return this.buttonText && this.buttonText.length > 0;
|
||||||
|
},
|
||||||
value: {
|
value: {
|
||||||
get() {
|
get() {
|
||||||
return this.modelValue;
|
return this.modelValue;
|
||||||
|
|
@ -227,6 +249,13 @@ export default {
|
||||||
if (this.meta.valid) {
|
if (this.meta.valid) {
|
||||||
this.$emit('click-event');
|
this.$emit('click-event');
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
async clickedTextButton() {
|
||||||
|
const result = await validate(this.value, this.validationRules);
|
||||||
|
if (result.valid) {
|
||||||
|
this.handleChange(this.value);
|
||||||
|
this.$emit('click-event');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -318,6 +347,19 @@ input::-webkit-date-and-time-value {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.has-text-button {
|
||||||
|
display: flex;
|
||||||
|
input[type='text'] {
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
min-width: 6.875rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
input {
|
input {
|
||||||
&.has-icon {
|
&.has-icon {
|
||||||
|
|
@ -338,8 +380,8 @@ input::-webkit-date-and-time-value {
|
||||||
.form-control {
|
.form-control {
|
||||||
border: $border-input;
|
border: $border-input;
|
||||||
border-radius: $border-radius;
|
border-radius: $border-radius;
|
||||||
min-height: 3rem;
|
min-height: 2.8125rem;
|
||||||
max-height: 3rem;
|
max-height: 2.8125rem;
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
letter-spacing: inherit;
|
letter-spacing: inherit;
|
||||||
box-shadow: $box-shadow-input;
|
box-shadow: $box-shadow-input;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ function processZipCodeResults(request) {
|
||||||
containsMilitaryBase: serviceZipValidationResponse.containsMilitaryBase,
|
containsMilitaryBase: serviceZipValidationResponse.containsMilitaryBase,
|
||||||
isValid: serviceZipValidationResponse.isValid,
|
isValid: serviceZipValidationResponse.isValid,
|
||||||
isServiceable: serviceZipValidationResponse.isServiceable,
|
isServiceable: serviceZipValidationResponse.isServiceable,
|
||||||
|
city: serviceZipValidationResponse.city,
|
||||||
state: serviceZipValidationResponse.state,
|
state: serviceZipValidationResponse.state,
|
||||||
zipCodeCtu: serviceZipValidationResponse.zipCodeCtu
|
zipCodeCtu: serviceZipValidationResponse.zipCodeCtu
|
||||||
})).catch(() => ({
|
})).catch(() => ({
|
||||||
|
|
|
||||||
|
|
@ -314,7 +314,6 @@ describe('Google Map', () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const wrapper = shallowMount(googleMap, {});
|
const wrapper = shallowMount(googleMap, {});
|
||||||
await awaitingSetupTicks(wrapper);
|
await awaitingSetupTicks(wrapper);
|
||||||
const numberOfExtendCallsDuringMount = 2;
|
|
||||||
|
|
||||||
mockGeocode.mockImplementation(() => {
|
mockGeocode.mockImplementation(() => {
|
||||||
const result = {
|
const result = {
|
||||||
|
|
@ -329,7 +328,7 @@ describe('Google Map', () => {
|
||||||
const result = wrapper.vm.getBounds(locations);
|
const result = wrapper.vm.getBounds(locations);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(mockExtend).toHaveBeenCalledTimes(numberOfExtendCallsDuringMount + numberOfExtendCallsPostMount);
|
expect(mockExtend).toHaveBeenCalledTimes(numberOfExtendCallsPostMount);
|
||||||
expect(result).toBe(mockLatLngBound);
|
expect(result).toBe(mockLatLngBound);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ export default {
|
||||||
const { AdvancedMarkerElement, PinElement } = await window.google.maps.importLibrary('marker');
|
const { AdvancedMarkerElement, PinElement } = await window.google.maps.importLibrary('marker');
|
||||||
markers?.forEach((marker, index) => {
|
markers?.forEach((marker, index) => {
|
||||||
const pinElement = new PinElement({
|
const pinElement = new PinElement({
|
||||||
glyph: String.fromCharCode('A'.charCodeAt(0) + index),
|
glyphText: String.fromCharCode('A'.charCodeAt(0) + index),
|
||||||
glyphColor: '#000000',
|
glyphColor: '#000000',
|
||||||
borderColor: '#000000'
|
borderColor: '#000000'
|
||||||
});
|
});
|
||||||
|
|
@ -107,9 +107,11 @@ export default {
|
||||||
async getBoundsFromAddress(address) {
|
async getBoundsFromAddress(address) {
|
||||||
await this.setGeocoder();
|
await this.setGeocoder();
|
||||||
const result = await this.geocoder.geocode({ address });
|
const result = await this.geocoder.geocode({ address });
|
||||||
return result?.results?.length > 0
|
if (result?.results?.length > 0) {
|
||||||
? result.results[0].geometry?.bounds
|
return result.results[0].geometry?.bounds ?? null;
|
||||||
: null;
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async createMapWithMarkersForAddresses(markers) {
|
async createMapWithMarkersForAddresses(markers) {
|
||||||
const markerPositions = await this.getLocationsFromAddresses(markers);
|
const markerPositions = await this.getLocationsFromAddresses(markers);
|
||||||
|
|
@ -117,9 +119,12 @@ export default {
|
||||||
const map = await this.getMap(zipBounds?.getCenter());
|
const map = await this.getMap(zipBounds?.getCenter());
|
||||||
|
|
||||||
await this.addMarkersToMap(map, markerPositions);
|
await this.addMarkersToMap(map, markerPositions);
|
||||||
|
let positionsToDisplay = markerPositions.map((marker) => marker.position);
|
||||||
|
if(zipBounds) {
|
||||||
|
positionsToDisplay.push(zipBounds.getNorthEast());
|
||||||
|
positionsToDisplay.push(zipBounds.getSouthWest());
|
||||||
|
}
|
||||||
|
|
||||||
const positionsToDisplay = markerPositions.map((marker) => marker.position)
|
|
||||||
.concat(zipBounds?.getNorthEast(), zipBounds?.getSouthWest());
|
|
||||||
const bounds = this.getBounds(positionsToDisplay);
|
const bounds = this.getBounds(positionsToDisplay);
|
||||||
map.fitBounds(bounds);
|
map.fitBounds(bounds);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
exports[`Shop list button should render correctly with all relevant props 1`] = `
|
exports[`Shop list button should render correctly with all relevant props 1`] = `
|
||||||
<transition-stub name="fade" mode="out-in" appear="false" persisted="false" css="true" selectedvalue="selected value">
|
<transition-stub name="fade" mode="out-in" appear="false" persisted="false" css="true" selectedvalue="selected value">
|
||||||
<base-input-button-stub modelvalue="value of modal" groupname="name of group" buttonwrapperclasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2" ismultiselect="false" validationrules="" isrequired="true" selectinginitiatesload="false" suppresserror="false" buttonlabel="Label of button" buttonlabelsubcopy="Sub copy of button" buttonbodycopy="button body copy" screenreaderonlytext="screen reader only text" additionalbuttondata="[object Object]" alttext="" iswide="false" value="1234"></base-input-button-stub>
|
<base-input-button-stub modelvalue="value of modal" groupname="name of group" buttonwrapperclasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 no-hover mb-2" ismultiselect="false" validationrules="" isrequired="true" selectinginitiatesload="false" suppresserror="false" buttonlabel="Label of button" buttonlabelsubcopy="Sub copy of button" buttonbodycopy="button body copy" screenreaderonlytext="screen reader only text" additionalbuttondata="[object Object]" alttext="" iswide="false" value="1234"></base-input-button-stub>
|
||||||
</transition-stub>
|
</transition-stub>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Shop list button should render correctly with required props 1`] = `
|
exports[`Shop list button should render correctly with required props 1`] = `
|
||||||
<transition-stub name="fade" mode="out-in" appear="false" persisted="false" css="true">
|
<transition-stub name="fade" mode="out-in" appear="false" persisted="false" css="true">
|
||||||
<base-input-button-stub modelvalue="value of modal" groupname="name of group" buttonwrapperclasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2" ismultiselect="false" validationrules="" isrequired="true" selectinginitiatesload="false" suppresserror="false" alttext="" iswide="false" value="1234"></base-input-button-stub>
|
<base-input-button-stub modelvalue="value of modal" groupname="name of group" buttonwrapperclasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 no-hover mb-2" ismultiselect="false" validationrules="" isrequired="true" selectinginitiatesload="false" suppresserror="false" alttext="" iswide="false" value="1234"></base-input-button-stub>
|
||||||
</transition-stub>
|
</transition-stub>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -149,10 +149,9 @@ describe('Shop list button', () => {
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(buttonLabelSubCopy.exists()).toBeTruthy();
|
expect(buttonLabelSubCopy.exists()).toBeTruthy();
|
||||||
expect(buttonLabelSubCopy.classes().length).toBe(4);
|
expect(buttonLabelSubCopy.classes().length).toBe(3);
|
||||||
expect(buttonLabelSubCopy.classes()).toContain('m-0');
|
expect(buttonLabelSubCopy.classes()).toContain('m-0');
|
||||||
expect(buttonLabelSubCopy.classes()).toContain('caption');
|
expect(buttonLabelSubCopy.classes()).toContain('button-label-sub-copy');
|
||||||
expect(buttonLabelSubCopy.classes()).toContain('ms-2');
|
|
||||||
expect(buttonLabelSubCopy.classes()).toContain(textPosition);
|
expect(buttonLabelSubCopy.classes()).toContain(textPosition);
|
||||||
});
|
});
|
||||||
test('when "textPosition" prop not provided', async () => {
|
test('when "textPosition" prop not provided', async () => {
|
||||||
|
|
@ -173,10 +172,9 @@ describe('Shop list button', () => {
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(buttonLabelSubCopy.exists()).toBeTruthy();
|
expect(buttonLabelSubCopy.exists()).toBeTruthy();
|
||||||
expect(buttonLabelSubCopy.classes().length).toBe(3);
|
expect(buttonLabelSubCopy.classes().length).toBe(2);
|
||||||
expect(buttonLabelSubCopy.classes()).toContain('m-0');
|
expect(buttonLabelSubCopy.classes()).toContain('m-0');
|
||||||
expect(buttonLabelSubCopy.classes()).toContain('caption');
|
expect(buttonLabelSubCopy.classes()).toContain('button-label-sub-copy');
|
||||||
expect(buttonLabelSubCopy.classes()).toContain('ms-2');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('availability indicator block with expected when displayAvailabilityIndicators true', () => {
|
describe('availability indicator block with expected when displayAvailabilityIndicators true', () => {
|
||||||
|
|
@ -378,8 +376,7 @@ describe('Shop list button', () => {
|
||||||
// Assert
|
// Assert
|
||||||
expect(buttonBodyCopy.exists()).toBeTruthy();
|
expect(buttonBodyCopy.exists()).toBeTruthy();
|
||||||
expect(buttonBodyCopy.classes()).toContain('m-0');
|
expect(buttonBodyCopy.classes()).toContain('m-0');
|
||||||
expect(buttonBodyCopy.classes()).toContain('button-label-sub-copy');
|
expect(buttonBodyCopy.classes()).toContain('button-body-copy');
|
||||||
expect(buttonBodyCopy.classes()).toContain('small');
|
|
||||||
});
|
});
|
||||||
test('screen reader only text when screenReaderOnlyText provided', async () => {
|
test('screen reader only text when screenReaderOnlyText provided', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
|
||||||
|
|
@ -5,35 +5,39 @@
|
||||||
<baseInputButton
|
<baseInputButton
|
||||||
v-bind="$props"
|
v-bind="$props"
|
||||||
v-model="selectedValue"
|
v-model="selectedValue"
|
||||||
buttonWrapperClasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2">
|
buttonWrapperClasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 no-hover mb-2">
|
||||||
<div
|
<div
|
||||||
:aria-label="buttonLabel"
|
:aria-label="buttonLabel"
|
||||||
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4">
|
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4">
|
||||||
<div class="row-one">
|
<div class="row-one">
|
||||||
|
<div class="availability-indicator-spacer"></div>
|
||||||
<span
|
<span
|
||||||
id="buttonLabelSpan"
|
id="buttonLabelSpan"
|
||||||
class="m-0 button-label-copy"
|
class="m-0 button-label-copy"
|
||||||
:class="textPosition">{{ buttonLabel }}
|
:class="textPosition">{{ buttonLabel }}
|
||||||
</span>
|
</span>
|
||||||
|
<span class="separator"></span>
|
||||||
<span
|
<span
|
||||||
id="buttonLabelSubCopySpan"
|
id="buttonLabelSubCopySpan"
|
||||||
class="m-0 caption ms-2"
|
class="m-0 button-label-sub-copy"
|
||||||
:class="textPosition">{{ buttonLabelSubCopy }}
|
:class="textPosition">{{ buttonLabelSubCopy }}
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div class="availability-indicator-container">
|
||||||
v-if="displayAvailabilityIndicators"
|
|
||||||
id="availabilityIndicatorBlock"
|
|
||||||
class="availability-indicator rounded-pill"
|
|
||||||
:class="availabilityRatingClass">
|
|
||||||
<div
|
<div
|
||||||
id="availabilityIndicator"
|
v-if="displayAvailabilityIndicators"
|
||||||
class="d-flex align-items-center">
|
id="availabilityIndicatorBlock"
|
||||||
|
class="availability-indicator rounded-pill"
|
||||||
|
:class="availabilityRatingClass">
|
||||||
<div
|
<div
|
||||||
id="availabilityBadge"
|
id="availabilityIndicator"
|
||||||
class="availability-badge"
|
class="d-flex align-items-center">
|
||||||
:class="availabilityRating == 'high' ? 'green' : 'orange'">
|
<div
|
||||||
|
id="availabilityBadge"
|
||||||
|
class="availability-badge"
|
||||||
|
:class="availabilityRatingClass">
|
||||||
|
</div>
|
||||||
|
<span class="m-0 button-auxillary-copy">{{ badgeText }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="m-0 button-auxillary-copy">{{ badgeText }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -41,7 +45,7 @@
|
||||||
<span
|
<span
|
||||||
v-if="buttonBodyCopy"
|
v-if="buttonBodyCopy"
|
||||||
id="buttonBodyCopy"
|
id="buttonBodyCopy"
|
||||||
class="m-0 button-label-sub-copy small"
|
class="m-0 button-body-copy"
|
||||||
v-html="buttonBodyCopy"></span>
|
v-html="buttonBodyCopy"></span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
|
|
@ -103,116 +107,128 @@ export default {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "@/styles/ux-variables-svg-strings.scss";
|
@import "@/styles/ux-variables-svg-strings.scss";
|
||||||
|
$heritage-border-radius: 60px;
|
||||||
|
$heritage-border-color: #CACBCC;
|
||||||
|
$heritage-box-shadow: 0 1px 5px rgba($black, 0.2);
|
||||||
|
$heritage-checked-background-color: #e7f1f6;
|
||||||
|
$heritage-checked-border-color: #0070d1;
|
||||||
|
|
||||||
.list-button {
|
.list-button {
|
||||||
outline: none;
|
outline: none;
|
||||||
input[type="radio"],
|
input[type="radio"],
|
||||||
input[type="checkbox"] {
|
input[type="checkbox"] {
|
||||||
position: static; //override bootstrap
|
position: static; //override bootstrap
|
||||||
|
&:checked + .list-button-content {
|
||||||
&:focus-visible + .list-button-content {
|
background: $heritage-checked-background-color;
|
||||||
box-shadow: 0 0 0 2.5px $blue;
|
border-color: $heritage-checked-border-color;
|
||||||
}
|
box-shadow: 0 0 0 1px $blue;
|
||||||
&:focus + .list-button-content {
|
.button-label-copy {
|
||||||
box-shadow: 0 0 0 2.5px $blue;
|
font-weight: 500;
|
||||||
}
|
color: $black;
|
||||||
&:checked + .list-button-content {
|
}
|
||||||
color: $black;
|
}
|
||||||
font-weight: 500;
|
&:focus:checked + .list-button-content {
|
||||||
background: $blue-100;
|
border-width: 2.5px;
|
||||||
box-shadow: 0 0 0 1px $blue;
|
}
|
||||||
}
|
}
|
||||||
&:checked:focus + .list-button-content {
|
|
||||||
box-shadow: 0 0 0 2.5px $blue;
|
|
||||||
}
|
|
||||||
&:checked + .list-button-content p,
|
|
||||||
&:checked + .list-button-content span {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.list-button-content {
|
.list-button-content {
|
||||||
color: $gray-600;
|
color: $darker-gray;
|
||||||
position: relative;
|
font-weight: $font-weight-normal;
|
||||||
background: $white;
|
position: relative;
|
||||||
transition: all 150ms linear;
|
background: $white;
|
||||||
border-radius: $border-radius-lg;
|
transition: all 150ms linear;
|
||||||
border: 1px solid $gray-500;
|
border-radius: $heritage-border-radius;
|
||||||
width: 100%;
|
border: 1px solid $heritage-border-color;
|
||||||
outline: none;
|
box-shadow: $heritage-box-shadow;
|
||||||
|
width: 100%;
|
||||||
span {
|
outline: none;
|
||||||
&.small {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: $gray-550;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.button-content {
|
.button-content {
|
||||||
row-gap: 0.25rem;
|
row-gap: 0.25rem;
|
||||||
|
|
||||||
.row-one {
|
.row-one {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
line-height: 1.5rem;
|
justify-content: center;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
|
||||||
.button-label-copy {
|
.separator {
|
||||||
font-weight: 500;
|
height: 1.25rem;
|
||||||
}
|
margin: 0rem .3125rem;
|
||||||
|
border-left: 1px solid $heritage-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
.availability-indicator {
|
.availability-indicator-spacer {
|
||||||
background-repeat: no-repeat;
|
width: 8rem;
|
||||||
display: flex;
|
margin-right: auto;
|
||||||
align-items: center;
|
}
|
||||||
margin-left: auto;
|
|
||||||
padding: 0.125rem 0.5rem;
|
|
||||||
|
|
||||||
.availability-badge {
|
.availability-indicator-container {
|
||||||
display: inline-flex;
|
display: flex;
|
||||||
width: 13px;
|
align-items: center;
|
||||||
height: 12px;
|
justify-content: flex-end;
|
||||||
background-position: center;
|
margin-left: auto;
|
||||||
background-repeat: no-repeat;
|
width: 8rem;
|
||||||
margin: 0 0.25rem 0 0;
|
}
|
||||||
|
|
||||||
&.green {
|
.availability-indicator {
|
||||||
background-image: url($svg-shop-list-button-green-availability);
|
background-repeat: no-repeat;
|
||||||
}
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.125rem 0.5rem;
|
||||||
|
|
||||||
&.orange {
|
.availability-badge {
|
||||||
background-image: url($svg-shop-list-button-orange-availability);
|
display: inline-flex;
|
||||||
}
|
width: 13px;
|
||||||
}
|
height: 12px;
|
||||||
.button-auxillary-copy {
|
background-position: center;
|
||||||
box-sizing: border-box;
|
background-repeat: no-repeat;
|
||||||
justify-content: right;
|
margin: 0 0.25rem 0 0;
|
||||||
line-height: 1.25rem;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.green {
|
&.green {
|
||||||
color: $green-700;
|
background-image: url($svg-shop-list-button-green-availability);
|
||||||
background-color: $green-100;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&.orange {
|
&.orange {
|
||||||
color: $orange-600;
|
background-image: url($svg-shop-list-button-orange-availability);
|
||||||
background-color: $orange-100;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&.gray {
|
&.gray {
|
||||||
color: $gray-600;
|
background-image: url(~@/assets/img/heritage-loader-blue.gif);
|
||||||
background-color: $gray-100;
|
background-size: contain;
|
||||||
padding-left: 0.125rem;
|
}
|
||||||
padding-right: 0.125rem;
|
}
|
||||||
}
|
.button-auxillary-copy {
|
||||||
}
|
box-sizing: border-box;
|
||||||
}
|
justify-content: right;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.row-two {
|
&.green {
|
||||||
text-align: left;
|
color: $green-700;
|
||||||
}
|
background-color: $green-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.orange {
|
||||||
|
color: $orange-600;
|
||||||
|
background-color: $orange-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.gray {
|
||||||
|
color: $gray-600;
|
||||||
|
background-color: $gray-100;
|
||||||
|
padding-left: 0.125rem;
|
||||||
|
padding-right: 0.125rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-two {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
||||||
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
|
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
|
||||||
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
|
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
|
||||||
import buttonMain from '@/ux-components/button-main/button-main.vue';
|
import buttonMain from '@/ux-components/button-main/button-main.vue';
|
||||||
import buttonVariants from '@/constants/button-variants';
|
import { buttonVariants } from '@/constants/component-variants';
|
||||||
|
|
||||||
// Supporting files
|
// Supporting files
|
||||||
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper.js';
|
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper.js';
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ import coverageType from '@/constants/coverage-type';
|
||||||
import alert from '@/ux-components/alert/alert.vue';
|
import alert from '@/ux-components/alert/alert.vue';
|
||||||
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
|
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
|
||||||
import buttonMain from '@/ux-components/button-main/button-main.vue';
|
import buttonMain from '@/ux-components/button-main/button-main.vue';
|
||||||
import buttonVariants from '@/constants/button-variants';
|
import { buttonVariants } from '@/constants/component-variants';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'policy-vehicles',
|
name: 'policy-vehicles',
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,21 @@
|
||||||
<template>
|
<template>
|
||||||
<transition
|
<transition
|
||||||
name="fade"
|
name="fade"
|
||||||
mode="out-in">
|
mode="out-in">
|
||||||
<div
|
<div
|
||||||
v-if="isDisplayed"
|
class="appointment-type-question"
|
||||||
class="appointment-type-question"
|
aria-live="polite">
|
||||||
aria-live="polite">
|
<buttonQuestion
|
||||||
<buttonQuestion
|
ref="buttonQuestion"
|
||||||
ref="buttonQuestion"
|
v-model="selectedValues"
|
||||||
v-model="selectedValues"
|
customButtonQuestionId="appointmentTypeQuestion"
|
||||||
customButtonQuestionId="appointmentTypeQuestion"
|
:answers="answersToDisplay"
|
||||||
:questionText="questionText"
|
:groupName="groupName"
|
||||||
:answers="answersToDisplay"
|
:suppressError="suppressError"
|
||||||
:groupName="groupName"
|
:validationRules="validationRules"
|
||||||
buttonTypeString="listCard"
|
isRequired />
|
||||||
:suppressError="suppressError"
|
</div>
|
||||||
:validationRules="validationRules"
|
</transition>
|
||||||
isRequired />
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -38,12 +35,9 @@ export default {
|
||||||
modelValue: String,
|
modelValue: String,
|
||||||
groupName: String,
|
groupName: String,
|
||||||
isAvailable: Boolean,
|
isAvailable: Boolean,
|
||||||
isDisplayed: Boolean,
|
|
||||||
suppressError: Boolean,
|
suppressError: Boolean,
|
||||||
validationRules: String,
|
validationRules: String,
|
||||||
cmsWidgetName: String,
|
cmsWidgetName: String
|
||||||
isServiceableMobile: Boolean,
|
|
||||||
isServiceableInshop: Boolean
|
|
||||||
},
|
},
|
||||||
emits: ['update:modelValue'],
|
emits: ['update:modelValue'],
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -51,23 +45,13 @@ export default {
|
||||||
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
|
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
|
||||||
},
|
},
|
||||||
answersFromCms() {
|
answersFromCms() {
|
||||||
const retCms = this.getCmsContent(this.cmsWidgetName, 'Answers');
|
return this.getCmsContent(this.cmsWidgetName, 'Answers');
|
||||||
if (retCms && retCms.length > 0 && this.isMobileFeeHidden) {
|
|
||||||
return retCms.map((x) => (x.Name === AppointmentTypeStrings.MOBILE ? { ...x, SubText: x.SubText.replace('*', '') } : x));
|
|
||||||
}
|
|
||||||
return retCms;
|
|
||||||
},
|
},
|
||||||
answersToDisplay() {
|
answersToDisplay() {
|
||||||
const shouldShowMobile = this.isServiceableMobile && this.isItacOrNoComp;
|
|
||||||
const shouldShowMobileNotITACNotNoComp = this.isServiceableMobile && !this.isItacOrNoComp;
|
|
||||||
const shouldShowInshop = this.isServiceableInshop;
|
|
||||||
const shouldShowDropoff = this.isServiceableInshop && !useMainStore().damage.isRepair;
|
|
||||||
return this.answersFromCms
|
return this.answersFromCms
|
||||||
? this.answersFromCms.filter((answer) => (
|
? this.answersFromCms.filter((answer) => (
|
||||||
(answer.Name === AppointmentTypeStrings.IN_SHOP && shouldShowInshop)
|
(answer.Name === AppointmentTypeStrings.IN_SHOP)
|
||||||
|| (answer.Name === AppointmentTypeStrings.MOBILE && shouldShowMobile)
|
|| (answer.Name === AppointmentTypeStrings.MOBILE)
|
||||||
|| (answer.Name === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP && shouldShowMobileNotITACNotNoComp)
|
|
||||||
|| (answer.Name === AppointmentTypeStrings.DROP_OFF && shouldShowDropoff)
|
|
||||||
))
|
))
|
||||||
: [];
|
: [];
|
||||||
},
|
},
|
||||||
|
|
@ -78,58 +62,13 @@ export default {
|
||||||
set(newValue) {
|
set(newValue) {
|
||||||
this.$emit('update:modelValue', newValue);
|
this.$emit('update:modelValue', newValue);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
isITAC() {
|
|
||||||
return useMainStore().isITAC;
|
|
||||||
},
|
|
||||||
isItacOrNoComp() {
|
|
||||||
return this.isITAC || useMainStore().isNoComp;
|
|
||||||
},
|
|
||||||
isMobileFeeHidden() {
|
|
||||||
return this.getSettingValue(experimentSettings.ISS_FEATURE_TOGGLE_IS_MOBILE_FEE_HIDDEN) === 'true';
|
|
||||||
},
|
|
||||||
isMobileOnly() {
|
|
||||||
return this.isServiceableMobile && !this.isServiceableInshop;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
answersToDisplay: {
|
|
||||||
handler(newValue) {
|
|
||||||
// If there is only one option to display and that option is 'Mobile' then select it
|
|
||||||
if (
|
|
||||||
newValue.length === 1
|
|
||||||
&& newValue.findIndex((answer) => (answer.Name === AppointmentTypeStrings.MOBILE
|
|
||||||
|| answer.Name === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP)) !== -1
|
|
||||||
) {
|
|
||||||
if (this.isItacOrNoComp) {
|
|
||||||
this.selectedValues = AppointmentTypeStrings.MOBILE;
|
|
||||||
} else {
|
|
||||||
this.selectedValues = AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
immediate: true
|
|
||||||
},
|
|
||||||
isMobileOnly: {
|
|
||||||
handler(newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
if (this.isItacOrNoComp) {
|
|
||||||
this.selectedValues = AppointmentTypeStrings.MOBILE;
|
|
||||||
} else {
|
|
||||||
this.selectedValues = AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
:deep(.button-question) {
|
.appointment-type-question {
|
||||||
.list-card img {
|
margin-bottom: 1.25rem;
|
||||||
height: auto;
|
|
||||||
width: 3.417rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,13 @@
|
||||||
|
/* eslint-env jest */
|
||||||
import baseMixin from '@/mixins/base-mixin';
|
import baseMixin from '@/mixins/base-mixin';
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { mount, flushPromises } from '@vue/test-utils';
|
||||||
import { getMountOptions } from '@/helpers/unit-test-helper.js';
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
|
import { AppointmentTypeStrings } from '@/constants/schedule-constants';
|
||||||
|
import navigationScenarios from '@/router/router-constants/navigation-scenarios';
|
||||||
|
import routerParams from '@/router/router-constants/router-params';
|
||||||
|
import { useMainStore } from '@/store';
|
||||||
import serviceLocation from '@/layouts/service-location/service-location.vue';
|
import serviceLocation from '@/layouts/service-location/service-location.vue';
|
||||||
|
import { getZipCodeData } from '@/helpers/service-location-helper';
|
||||||
// Define Mocks
|
|
||||||
jest.mock('@/helpers/cms-content-helper', () => ({
|
|
||||||
fetchCmsContentForPage: jest.fn(() => Promise.resolve('content'))
|
|
||||||
}));
|
|
||||||
|
|
||||||
/** @ignore */
|
|
||||||
function setupMocks() {
|
|
||||||
const wrapper = shallowMount(
|
|
||||||
serviceLocation,
|
|
||||||
getMountOptions({
|
|
||||||
router: {
|
|
||||||
navigate: jest.fn()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
wrapper.vm.$router.navigateWithSpinner = jest.fn();
|
|
||||||
wrapper.vm.navigateBack = baseMixin.methods.navigateBack;
|
|
||||||
|
|
||||||
return { wrapper };
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockGetServiceabilityDetails = () => {
|
const mockGetServiceabilityDetails = () => {
|
||||||
const serviceabilityDetails = {
|
const serviceabilityDetails = {
|
||||||
|
|
@ -35,123 +19,199 @@ const mockGetServiceabilityDetails = () => {
|
||||||
|
|
||||||
return Promise.resolve(serviceabilityDetails);
|
return Promise.resolve(serviceabilityDetails);
|
||||||
};
|
};
|
||||||
|
const mockZipcodeData = (zip) => {
|
||||||
|
if (zip === '43235' || zip === '55555') {
|
||||||
|
return Promise.resolve({
|
||||||
|
containsMilitaryBase: false,
|
||||||
|
isValid: true,
|
||||||
|
isServiceable: true,
|
||||||
|
city: 'Columbus',
|
||||||
|
state: 'OH',
|
||||||
|
zipCodeCtu: '01820'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zip === '45433') {
|
||||||
|
return Promise.resolve({
|
||||||
|
containsMilitaryBase: true,
|
||||||
|
isValid: true,
|
||||||
|
isServiceable: true,
|
||||||
|
city: 'Columbus',
|
||||||
|
state: 'OH',
|
||||||
|
zipCodeCtu: '01820'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve({
|
||||||
|
containsMilitaryBase: false,
|
||||||
|
isValid: false,
|
||||||
|
isServiceable: false,
|
||||||
|
city: null,
|
||||||
|
state: null,
|
||||||
|
zipCodeCtu: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const mockProviders = () => {
|
||||||
|
return Promise.resolve([
|
||||||
|
{
|
||||||
|
"address": {
|
||||||
|
"city": "COLUMBUS",
|
||||||
|
"country": "US",
|
||||||
|
"state": "OH",
|
||||||
|
"streetAddress": "6826 Sawmill Rd",
|
||||||
|
"streetAddress2": "",
|
||||||
|
"zipCode": "43235",
|
||||||
|
"zipCodeCtu": "03357"
|
||||||
|
},
|
||||||
|
"distanceInMiles": 4.136335989015438,
|
||||||
|
"providerNumber": "003357",
|
||||||
|
"companyName": "SAFELITE AUTOGLASS - COLUMBUS, OH",
|
||||||
|
"phoneNumber": "6142336400",
|
||||||
|
"isSafeliteShop": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": {
|
||||||
|
"city": "Lewis Center",
|
||||||
|
"country": "US",
|
||||||
|
"state": "OH",
|
||||||
|
"streetAddress": "1343 Cameron Ave",
|
||||||
|
"streetAddress2": "",
|
||||||
|
"zipCode": "43035",
|
||||||
|
"zipCodeCtu": "03357"
|
||||||
|
},
|
||||||
|
"distanceInMiles": 8.193072412262042,
|
||||||
|
"providerNumber": "003417",
|
||||||
|
"companyName": "SAFELITE AUTOGLASS - LEWIS CENTER, OH",
|
||||||
|
"phoneNumber": "6147815433",
|
||||||
|
"isSafeliteShop": true
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'@/helpers/service-location-helper',
|
'@/helpers/service-location-helper',
|
||||||
() => ({
|
() => ({
|
||||||
getServiceabilityDetails: jest.fn((mockServiceZipCode) => mockGetServiceabilityDetails(mockServiceZipCode))
|
getServiceabilityDetails: jest.fn((mockServiceZipCode) => mockGetServiceabilityDetails(mockServiceZipCode)),
|
||||||
|
getZipCodeData: jest.fn((mockServiceZipCode) => mockZipcodeData(mockServiceZipCode)),
|
||||||
|
getMobileZipCodeData: jest.fn((mockServiceZipCode) => mockZipcodeData(mockServiceZipCode))
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const mockMixin = {
|
const mockRoute = {
|
||||||
methods: {
|
params: {}
|
||||||
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
};
|
||||||
// linkWidgetName is not defined. This whole spec file needs review.
|
const mockRouter = {
|
||||||
if (widgetName === linkWidgetName) {
|
navigate: jest.fn()
|
||||||
return mockLinkCmsContent[cmsFieldName];
|
};
|
||||||
|
const mountOptions = {
|
||||||
|
global: {
|
||||||
|
mixins: [
|
||||||
|
{
|
||||||
|
computed: {
|
||||||
|
navigationScenarios() {
|
||||||
|
return navigationScenarios;
|
||||||
|
},
|
||||||
|
routerParams() {
|
||||||
|
return routerParams;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getCmsContent: jest.fn(),
|
||||||
|
getFooterInfoBoxHeight: jest.fn(() => 80),
|
||||||
|
getPageNameByQueryString: jest.fn(() => '')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
],
|
||||||
if (widgetName === modalWidgetName) {
|
mocks: {
|
||||||
return mockModalCmsContent[cmsFieldName];
|
$route: mockRoute,
|
||||||
}
|
$router: mockRouter
|
||||||
|
},
|
||||||
return null;
|
stubs: {
|
||||||
}),
|
alert: true,
|
||||||
|
textBlock: true,
|
||||||
getZipCodeData: jest.fn((zip) => {
|
appointmentTypeQuestion: true,
|
||||||
if (zip === '43235' || zip === '55555') {
|
contentGroupModal: true,
|
||||||
return Promise.resolve({
|
siteFooter: true,
|
||||||
containsMilitaryBase: false,
|
siteHeader: true,
|
||||||
isValid: true,
|
siteSubHeader: true,
|
||||||
state: 'OH',
|
shopAddress: true,
|
||||||
zipCodeCtu: '01820'
|
serviceZipQuestion: true
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (zip === '45433') {
|
|
||||||
return Promise.resolve({
|
|
||||||
containsMilitaryBase: true,
|
|
||||||
isValid: true,
|
|
||||||
state: 'OH'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve({
|
|
||||||
containsMilitaryBase: false,
|
|
||||||
isValid: false,
|
|
||||||
state: null,
|
|
||||||
zipCodeCtu: null
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
onSubmit: jest.fn(),
|
|
||||||
onInvalidSubmit: jest.fn()
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
function setupMocks({ appointmentType = AppointmentTypeStrings.IN_SHOP }) {
|
||||||
|
mountOptions.global.plugins = [createTestingPinia({
|
||||||
|
initialState: {
|
||||||
|
main: {
|
||||||
|
order: {
|
||||||
|
serviceLocation: {
|
||||||
|
appointmentType: appointmentType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})];
|
||||||
|
useMainStore().getProviders = jest.fn().mockImplementation(() => mockProviders());
|
||||||
|
const wrapper = mount(serviceLocation, mountOptions);
|
||||||
|
|
||||||
describe('navigation', () => {
|
wrapper.vm.$router.navigateWithSpinner = jest.fn();
|
||||||
test('if the back button is clicked, navigate back', async () => {
|
wrapper.vm.navigateBack = baseMixin.methods.navigateBack;
|
||||||
// Arrange
|
|
||||||
const { wrapper } = setupMocks({
|
return { wrapper };
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
describe('service-location.vue', () => {
|
||||||
|
describe('navigation', () => {
|
||||||
|
test('if the back button is clicked, navigate back', async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await wrapper.vm.navigateBack();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.$router.navigateWithSpinner).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('updating service zip', () => {
|
||||||
|
test('updates the page model after changing the service zip code', () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({ appointmentType: AppointmentTypeStrings.IN_SHOP });
|
||||||
|
|
||||||
|
const newServiceZipCode = '61606';
|
||||||
|
|
||||||
|
const shopAddressComponent = wrapper.findComponent({
|
||||||
|
ref: 'shopAddress'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
shopAddressComponent.vm.$emit('zip-updated', newServiceZipCode);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.zipCode).toStrictEqual(newServiceZipCode);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
test('displays military zip message when zip is updated', async () => {
|
||||||
await wrapper.vm.navigateBack();
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({ appointmentType: AppointmentTypeStrings.MOBILE });
|
||||||
|
|
||||||
// Assert
|
const mobileServiceZipCodeQuestion = wrapper.findComponent({
|
||||||
expect(wrapper.vm.$router.navigateWithSpinner).toHaveBeenCalled();
|
ref: 'mobileServiceZipCodeQuestion'
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
expect(wrapper.vm.zipContainsMilitaryBase).toBe(false);
|
||||||
describe('updating service zip', () => {
|
|
||||||
test('updates the page model after changing the service zip code', () => {
|
const newMobileServiceZipCode = '45433'
|
||||||
// Arrange
|
|
||||||
const { wrapper } = setupMocks({
|
// Act
|
||||||
mixins: [mockMixin]
|
mobileServiceZipCodeQuestion.vm.$emit('update:modelValue', newMobileServiceZipCode);
|
||||||
});
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
const newServiceZipCodeQuestion = {
|
// Assert
|
||||||
zipCode: '61606',
|
expect(wrapper.vm.zipContainsMilitaryBase).toBe(true);
|
||||||
state: 'IL'
|
});
|
||||||
};
|
|
||||||
|
|
||||||
const serviceZipCodeComponent = wrapper.findComponent({
|
|
||||||
ref: 'serviceZipCodeQuestion'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Act
|
|
||||||
serviceZipCodeComponent.vm.$emit('update:modelValue', newServiceZipCodeQuestion);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(wrapper.vm.serviceZipCodeQuestion).toStrictEqual(newServiceZipCodeQuestion);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('displays military zip message when zip is updated', () => {
|
|
||||||
// Arrange
|
|
||||||
const { wrapper } = setupMocks({});
|
|
||||||
|
|
||||||
const mobileLocationQuestionsComponent = wrapper.findComponent({
|
|
||||||
ref: 'mobileLocationQuestions'
|
|
||||||
});
|
|
||||||
mobileLocationQuestionsComponent.resetComponent = jest.fn();
|
|
||||||
|
|
||||||
const serviceZipCodeComponent = wrapper.findComponent({
|
|
||||||
ref: 'serviceZipCodeQuestion'
|
|
||||||
});
|
|
||||||
serviceZipCodeComponent.resetMobileFeePart = jest.fn();
|
|
||||||
|
|
||||||
expect(wrapper.vm.zipContainsMilitaryBase).toBe(false);
|
|
||||||
|
|
||||||
// TODO: Use or remove
|
|
||||||
const newServiceZipCodeQuestion = {
|
|
||||||
zipCode: '45433',
|
|
||||||
state: 'OH'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
serviceZipCodeComponent.vm.$emit('updated-contains-military-base', true);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(wrapper.vm.zipContainsMilitaryBase).toBe(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -12,28 +12,7 @@
|
||||||
<div class="service-location-container iss-heritage-content-container-width">
|
<div class="service-location-container iss-heritage-content-container-width">
|
||||||
<siteSubHeader
|
<siteSubHeader
|
||||||
cmsWidgetName="SiteSubHeaderWidget"
|
cmsWidgetName="SiteSubHeaderWidget"
|
||||||
class="mt-4" />
|
class="subheader" />
|
||||||
<serviceZipModalQuestion
|
|
||||||
ref="serviceZipCodeQuestion"
|
|
||||||
v-model="serviceZipCodeQuestion"
|
|
||||||
modalWidgetName="ServiceZipModalWidget"
|
|
||||||
:onZipUpdateCallback="reloadShopData"
|
|
||||||
@updatedServiceability="setServiceabilityDetails"
|
|
||||||
@updatedContainsMilitaryBase="
|
|
||||||
setContainsMilitaryBase
|
|
||||||
" />
|
|
||||||
<alert
|
|
||||||
v-if="displayMilitaryZipAlert"
|
|
||||||
ref="alertMilitaryBaseZip"
|
|
||||||
class="my-5"
|
|
||||||
cmsWidgetName="AlertMilitaryBaseZipWidget"
|
|
||||||
alertClass="alert-warning" />
|
|
||||||
<alert
|
|
||||||
v-if="displayServiceableMobileOnly"
|
|
||||||
ref="alertMobileOnly"
|
|
||||||
class="my-5"
|
|
||||||
cmsWidgetName="AlertMobileOnlyWidget"
|
|
||||||
alertClass="alert-warning" />
|
|
||||||
<alert
|
<alert
|
||||||
v-if="displayRecalibrationWarning"
|
v-if="displayRecalibrationWarning"
|
||||||
ref="alertRecalNoMobile"
|
ref="alertRecalNoMobile"
|
||||||
|
|
@ -41,18 +20,6 @@
|
||||||
cmsWidgetName="AlertRecalNoMobileWidget"
|
cmsWidgetName="AlertRecalNoMobileWidget"
|
||||||
alertClass="alert-warning"
|
alertClass="alert-warning"
|
||||||
@text-link-clicked="openModalAction" />
|
@text-link-clicked="openModalAction" />
|
||||||
<alert
|
|
||||||
v-if="displayServiceableInshopOnly"
|
|
||||||
ref="alertInshopOnly"
|
|
||||||
class="my-5"
|
|
||||||
cmsWidgetName="AlertInshopOnlyWidget"
|
|
||||||
alertClass="alert-warning" />
|
|
||||||
<alert
|
|
||||||
v-if="displayNoShopsAlert"
|
|
||||||
ref="alertNoShops"
|
|
||||||
class="my-5"
|
|
||||||
cmsWidgetName="AlertNoShopsWidget"
|
|
||||||
alertClass="alert-warning" />
|
|
||||||
<alert
|
<alert
|
||||||
v-if="displayBigTruckNoShops"
|
v-if="displayBigTruckNoShops"
|
||||||
ref="alertBigTruckNoShops"
|
ref="alertBigTruckNoShops"
|
||||||
|
|
@ -60,41 +27,94 @@
|
||||||
cmsWidgetName="AlertBigTruckNoShopsWidget"
|
cmsWidgetName="AlertBigTruckNoShopsWidget"
|
||||||
alertClass="alert-warning" />
|
alertClass="alert-warning" />
|
||||||
<div class="appointment-type">
|
<div class="appointment-type">
|
||||||
|
<div class="appointment-type-question-text d-flex">
|
||||||
|
<span class="w-100 recal-text" v-if="recalibrationRequired">{{ scheduleRecalText }}</span>
|
||||||
|
<span class="w-100" v-if="!requiresInshopRecalibration">{{ appointmentQuestionText }}</span>
|
||||||
|
</div>
|
||||||
|
<alert
|
||||||
|
v-if="displayServiceableInshopOnly"
|
||||||
|
ref="alertInshopOnly"
|
||||||
|
cmsWidgetName="AlertInshopOnlyWidget"
|
||||||
|
:manualCopy="inShopOnlyCopy"
|
||||||
|
:isCollapsible="true"
|
||||||
|
alertClass="alert-warning" />
|
||||||
|
<alert
|
||||||
|
v-if="displayServiceableMobileOnly"
|
||||||
|
ref="alertMobileOnly"
|
||||||
|
cmsWidgetName="AlertMobileOnlyWidget"
|
||||||
|
:manualCopy="mobileOnlyCopy"
|
||||||
|
:isCollapsible="true"
|
||||||
|
alertClass="alert-warning" />
|
||||||
<appointmentTypeQuestion
|
<appointmentTypeQuestion
|
||||||
v-show="isAppointmentTypeDisplayed"
|
v-if="!requiresInshopRecalibration && isServiceableMobile"
|
||||||
ref="appointmentTypeQuestion"
|
ref="appointmentTypeQuestion"
|
||||||
v-model="selectedAppointmentType"
|
v-model="selectedAppointmentType"
|
||||||
:isServiceableMobile="isServiceableMobile"
|
|
||||||
:isServiceableInshop="isServiceableInshop"
|
|
||||||
:isDisplayed="isAppointmentTypeDisplayed"
|
|
||||||
groupName="appointmentTypeQuestion"
|
groupName="appointmentTypeQuestion"
|
||||||
cmsWidgetName="AppointmentTypeQuestionWidget"
|
cmsWidgetName="AppointmentTypeQuestionWidget"
|
||||||
validationRules="option-required" />
|
validationRules="option-required" />
|
||||||
</div>
|
</div>
|
||||||
<mobileLocationModalQuestions
|
<div
|
||||||
v-if="isMobileLocationDisplayed"
|
v-if="isInshop">
|
||||||
ref="mobileLocationQuestions"
|
<alert
|
||||||
v-model="mobileLocationQuestions"
|
v-if="displayLowAvailabilityInshop"
|
||||||
customComponentId="mobileLocationQuestions"
|
ref="alertLowAvailabilityInshop"
|
||||||
:mobileFeePart="mobileFeePart"
|
cmsWidgetName="AlertLowAvailabilityInshopWidget"
|
||||||
validationRules="mobile-location-required"
|
:isCollapsible="true"
|
||||||
linkWidgetName="MobileLocationLinkWidget"
|
alertClass="alert-warning" />
|
||||||
modalWidgetName="MobileLocationModalWidget"
|
<shopAddress
|
||||||
:onZipUpdateCallback="reloadShopData"
|
ref="shopAddress"
|
||||||
@updated-mobile-fee-part="setMobileFeePart"
|
v-model="selectedProvider"
|
||||||
@updated-serviceability="setServiceabilityDetails"
|
:serviceZipcode="zipCode"
|
||||||
@updated-contains-military-base="setContainsMilitaryBase"
|
:selectedAppointmentType="selectedAppointmentType"
|
||||||
@updated-mobile-ctu="setCtuForMobile" />
|
modalWidgetName="ChangeLocationModalWidget"
|
||||||
<shopQuestion
|
cmsWidgetName="ShopAddressWidget"
|
||||||
v-show="isShopQuestionDisplayed"
|
@zip-updated="handleInShopZipUpdated" />
|
||||||
:ref="SHOP_QUESTION_REF_NAME"
|
<!-- INTEGRATION TODO: Put this right under the calendar -->
|
||||||
v-model="selectedProvider"
|
<alert
|
||||||
:selectedAppointmentType="selectedAppointmentType"
|
v-if="selectedProvider === null"
|
||||||
:isDisplayed="isShopQuestionDisplayed"
|
ref="alertNoAvailableShops"
|
||||||
cmsWidgetName="ShopQuestionWidget"
|
cmsWidgetName="AlertNoAvailableShopsWidget"
|
||||||
@updatedMobileProviderNumber="
|
:isCollapsible="true"
|
||||||
setMobileProviderNumber
|
alertClass="alert-danger" />
|
||||||
" />
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="isMobile">
|
||||||
|
<div class="expandable-link-container">
|
||||||
|
<a
|
||||||
|
v-if="displayMilitaryZipAlert && !militaryBaseWarningExpanded"
|
||||||
|
href="#"
|
||||||
|
class="expandable-link"
|
||||||
|
@click.prevent="militaryBaseWarningExpanded = true">
|
||||||
|
{{ militaryBaseWarningLinkText }}
|
||||||
|
</a>
|
||||||
|
<div
|
||||||
|
v-if="displayMilitaryZipAlert && militaryBaseWarningExpanded"
|
||||||
|
class="expandable-link-text">
|
||||||
|
{{ militaryBaseWarningText }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<serviceZipQuestion
|
||||||
|
ref="mobileServiceZipCodeQuestion"
|
||||||
|
v-model="mobileZipCode"
|
||||||
|
customInputId="mobileServiceZipCode"
|
||||||
|
class="mobile-service-zip-question"
|
||||||
|
:placeholderText="mobileZipPlaceholder"
|
||||||
|
:serviceZipFormatErrorMessage="errorMessages.MOBILE_SERVICE_ZIP_FORMAT"
|
||||||
|
:hasError="mobileZipError !== ''"
|
||||||
|
cmsWidgetName="MobileZipWidget" />
|
||||||
|
<div
|
||||||
|
v-if="mobileZipError"
|
||||||
|
ref="errorMessageDiv"
|
||||||
|
class="row my-1 form-test-error">
|
||||||
|
<span
|
||||||
|
class="d-inline-flex mt-0"
|
||||||
|
role="alert">{{ mobileZipError }}</span>
|
||||||
|
</div>
|
||||||
|
<textBlock
|
||||||
|
v-if="displayMobileFeeDisclaimer"
|
||||||
|
cmsWidgetName="MobileFeeDisclaimerWidget"
|
||||||
|
typeStyle="disclaimer" />
|
||||||
|
</div>
|
||||||
<contentGroupModal
|
<contentGroupModal
|
||||||
:ref="RECAL_MODAL_REF_NAME"
|
:ref="RECAL_MODAL_REF_NAME"
|
||||||
cssModalHeadlineClass="text-center"
|
cssModalHeadlineClass="text-center"
|
||||||
|
|
@ -104,7 +124,7 @@
|
||||||
class="mt-5"
|
class="mt-5"
|
||||||
cmsWidgetName="SiteFooterWidget"
|
cmsWidgetName="SiteFooterWidget"
|
||||||
:isForwardActionDisabled="
|
:isForwardActionDisabled="
|
||||||
!meta.valid || displayNoShopsAlert
|
!meta.valid || displayNoShopsAlert || displayBigTruckNoShops
|
||||||
"
|
"
|
||||||
@backClicked="navigateBack(this, navigateBackScenario)"
|
@backClicked="navigateBack(this, navigateBackScenario)"
|
||||||
@forwardClicked="forwardButtonAction" />
|
@forwardClicked="forwardButtonAction" />
|
||||||
|
|
@ -118,44 +138,31 @@
|
||||||
import { AppointmentTypeStrings } from '@/constants/schedule-constants.js';
|
import { AppointmentTypeStrings } from '@/constants/schedule-constants.js';
|
||||||
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
|
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
|
||||||
import settleAllPromises from '@/helpers/layout-helper';
|
import settleAllPromises from '@/helpers/layout-helper';
|
||||||
import { required } from '@/helpers/validation-rules';
|
|
||||||
import errorMessages from '@/constants/error-messages';
|
import errorMessages from '@/constants/error-messages';
|
||||||
import { useMainStore } from '@/store';
|
import { useMainStore } from '@/store';
|
||||||
import {
|
import {
|
||||||
getPricedMobileFeePart,
|
getPricedMobileFeePart,
|
||||||
getServiceabilityDetails,
|
getServiceabilityDetails,
|
||||||
getZipCodeData
|
getZipCodeData,
|
||||||
|
getMobileZipCodeData
|
||||||
} from '@/helpers/service-location-helper';
|
} from '@/helpers/service-location-helper';
|
||||||
|
import { toTitleCase } from '@/helpers/text-helper.js';
|
||||||
|
import { getAvailabilityRating } from '@/helpers/service-location-helper';
|
||||||
|
|
||||||
// Import Component
|
// Import Component
|
||||||
import alert from '@/ux-components/alert/alert.vue';
|
import alert from '@/ux-components/alert/alert.vue';
|
||||||
|
import textBlock from '@/digital-components/text-block/text-block.vue';
|
||||||
import appointmentTypeQuestion from '@/layouts/service-location/appointment-type-question/appointment-type-question.vue';
|
import appointmentTypeQuestion from '@/layouts/service-location/appointment-type-question/appointment-type-question.vue';
|
||||||
import baseFormMixin from '@/mixins/base-form-mixin';
|
import baseFormMixin from '@/mixins/base-form-mixin';
|
||||||
import contentGroupModal from '@/iss-components/content-group-modal/content-group-modal.vue';
|
import contentGroupModal from '@/iss-components/content-group-modal/content-group-modal.vue';
|
||||||
import mobileLocationModalQuestions from '@/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue';
|
|
||||||
import { Form, defineRule } from 'vee-validate';
|
import { Form, defineRule } from 'vee-validate';
|
||||||
import shopQuestion from '@/layouts/service-location/shop-question/shop-question.vue';
|
|
||||||
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
||||||
import siteHeader from '@/iss-components/site-header/site-header.vue';
|
import siteHeader from '@/iss-components/site-header/site-header.vue';
|
||||||
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
|
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
|
||||||
import serviceZipModalQuestion from '@/layouts/service-location/service-zip-modal-question/service-zip-modal-question.vue';
|
import shopAddress from '@/layouts/service-location/shop-address/shop-address.vue';
|
||||||
|
import serviceZipQuestion from '@/layouts/service-location/service-zip-question/service-zip-question.vue';
|
||||||
|
import widgetFields from '@/constants/cms-widget-fields';
|
||||||
|
|
||||||
// DEFINE VALIDATION RULES
|
|
||||||
defineRule('mobile-location-required', (value) => {
|
|
||||||
if (
|
|
||||||
value.addressQuestions.streetAddress === ''
|
|
||||||
|| value.addressQuestions.city === ''
|
|
||||||
|| value.addressQuestions.state === ''
|
|
||||||
|| value.addressQuestions.zipCode === ''
|
|
||||||
|| value.isVehicleProtected == null
|
|
||||||
) {
|
|
||||||
return errorMessages.MOBILE_LOCATION_REQUIRED;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
defineRule('selection-required', required(errorMessages.OPTION_REQUIRED));
|
|
||||||
|
|
||||||
const SHOP_QUESTION_REF_NAME = 'shopQuestion';
|
|
||||||
const RECAL_MODAL_REF_NAME = 'RecalModal';
|
const RECAL_MODAL_REF_NAME = 'RecalModal';
|
||||||
const SITE_FOOTER_REF_NAME = 'siteFooter';
|
const SITE_FOOTER_REF_NAME = 'siteFooter';
|
||||||
|
|
||||||
|
|
@ -163,16 +170,16 @@ export default {
|
||||||
name: 'service-location',
|
name: 'service-location',
|
||||||
components: {
|
components: {
|
||||||
alert,
|
alert,
|
||||||
|
textBlock,
|
||||||
appointmentTypeQuestion,
|
appointmentTypeQuestion,
|
||||||
contentGroupModal,
|
contentGroupModal,
|
||||||
mobileLocationModalQuestions,
|
|
||||||
siteFooter,
|
siteFooter,
|
||||||
siteHeader,
|
siteHeader,
|
||||||
siteSubHeader,
|
siteSubHeader,
|
||||||
// eslint-disable-next-line vue/no-reserved-component-names
|
// eslint-disable-next-line vue/no-reserved-component-names
|
||||||
Form,
|
Form,
|
||||||
serviceZipModalQuestion,
|
shopAddress,
|
||||||
shopQuestion
|
serviceZipQuestion
|
||||||
},
|
},
|
||||||
mixins: [baseFormMixin],
|
mixins: [baseFormMixin],
|
||||||
async beforeRouteEnter(to, from, next) {
|
async beforeRouteEnter(to, from, next) {
|
||||||
|
|
@ -182,8 +189,8 @@ export default {
|
||||||
const zipCodeData = getZipCodeData(serviceZipCode);
|
const zipCodeData = getZipCodeData(serviceZipCode);
|
||||||
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode);
|
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode);
|
||||||
const serviceabilityDetailsPromise = getServiceabilityDetails(serviceZipCode);
|
const serviceabilityDetailsPromise = getServiceabilityDetails(serviceZipCode);
|
||||||
const shopQuestionInitialDataPromise = shopQuestion.methods.loadInitialData(serviceZipCode);
|
|
||||||
const getGlassFeesPromise = useMainStore().getGlassFees();
|
const getGlassFeesPromise = useMainStore().getGlassFees();
|
||||||
|
const providersPromise = useMainStore().getProviders(serviceZipCode);
|
||||||
|
|
||||||
// Settle promises and get results
|
// Settle promises and get results
|
||||||
const promiseResultMap = [
|
const promiseResultMap = [
|
||||||
|
|
@ -203,13 +210,13 @@ export default {
|
||||||
resultKey: 'serviceabilityDetails',
|
resultKey: 'serviceabilityDetails',
|
||||||
promise: serviceabilityDetailsPromise
|
promise: serviceabilityDetailsPromise
|
||||||
},
|
},
|
||||||
{
|
|
||||||
resultKey: 'shopQuestionInitialData',
|
|
||||||
promise: shopQuestionInitialDataPromise
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
resultKey: 'zipCodeData',
|
resultKey: 'zipCodeData',
|
||||||
promise: zipCodeData
|
promise: zipCodeData
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resultKey: 'providers',
|
||||||
|
promise: providersPromise
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -221,10 +228,9 @@ export default {
|
||||||
resultMap.zipCodeData,
|
resultMap.zipCodeData,
|
||||||
resultMap.serviceabilityDetails,
|
resultMap.serviceabilityDetails,
|
||||||
resultMap.mobileFeePart,
|
resultMap.mobileFeePart,
|
||||||
resultMap.shopQuestionInitialData?.mobileProviderNumber,
|
resultMap.glassFees,
|
||||||
resultMap.glassFees
|
resultMap.providers
|
||||||
);
|
);
|
||||||
vm.$refs[SHOP_QUESTION_REF_NAME].initializeComponent(resultMap.shopQuestionInitialData);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
|
|
@ -238,7 +244,6 @@ export default {
|
||||||
city: this.getServiceCityFromStore(),
|
city: this.getServiceCityFromStore(),
|
||||||
state: this.getServiceStateFromStore(),
|
state: this.getServiceStateFromStore(),
|
||||||
zipCode: this.getServiceZipCodeFromStore(),
|
zipCode: this.getServiceZipCodeFromStore(),
|
||||||
isVehicleProtected: this.getIsVehicleProtectedFromStore(),
|
|
||||||
isGlassServiceableInshop: null,
|
isGlassServiceableInshop: null,
|
||||||
isRecalibrationServiceableInshop: null,
|
isRecalibrationServiceableInshop: null,
|
||||||
isGlassServiceableMobile: null,
|
isGlassServiceableMobile: null,
|
||||||
|
|
@ -249,9 +254,13 @@ export default {
|
||||||
mobileProviderNumber: null,
|
mobileProviderNumber: null,
|
||||||
zipContainsMilitaryBase: false,
|
zipContainsMilitaryBase: false,
|
||||||
zipCodeCtu: null,
|
zipCodeCtu: null,
|
||||||
SHOP_QUESTION_REF_NAME,
|
mobileZipCode: '',
|
||||||
|
mobileZipError: '',
|
||||||
|
militaryBaseWarningExpanded: false,
|
||||||
|
availabilityRating: null,
|
||||||
RECAL_MODAL_REF_NAME,
|
RECAL_MODAL_REF_NAME,
|
||||||
SITE_FOOTER_REF_NAME
|
SITE_FOOTER_REF_NAME,
|
||||||
|
errorMessages
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -264,61 +273,6 @@ export default {
|
||||||
answersFromCms() {
|
answersFromCms() {
|
||||||
return this.getCmsContent('ServiceTypeQuestionWidget', 'Answers');
|
return this.getCmsContent('ServiceTypeQuestionWidget', 'Answers');
|
||||||
},
|
},
|
||||||
serviceZipCodeQuestion: {
|
|
||||||
get() {
|
|
||||||
return {
|
|
||||||
state: this.state,
|
|
||||||
zipCode: this.zipCode
|
|
||||||
};
|
|
||||||
},
|
|
||||||
set(newValue) {
|
|
||||||
if (newValue.zipCode !== this.zipCode) {
|
|
||||||
this.resetMobileLocation();
|
|
||||||
this.selectedAppointmentType = null;
|
|
||||||
this.selectedProvider = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state = newValue.state;
|
|
||||||
this.zipCode = newValue.zipCode;
|
|
||||||
|
|
||||||
// eslint-disable-next-line vue/valid-next-tick
|
|
||||||
this.$nextTick();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mobileLocationQuestions: {
|
|
||||||
get() {
|
|
||||||
return {
|
|
||||||
addressQuestions: {
|
|
||||||
streetAddress: this.streetAddress,
|
|
||||||
streetAddress2: this.streetAddress2,
|
|
||||||
city: this.city,
|
|
||||||
state: this.state,
|
|
||||||
zipCode: this.zipCode
|
|
||||||
},
|
|
||||||
isVehicleProtected: this.isVehicleProtected
|
|
||||||
};
|
|
||||||
},
|
|
||||||
set(newValue) {
|
|
||||||
this.streetAddress = newValue.addressQuestions.streetAddress;
|
|
||||||
this.streetAddress2 = newValue.addressQuestions.streetAddress2;
|
|
||||||
this.city = newValue.addressQuestions.city;
|
|
||||||
this.state = newValue.addressQuestions.state;
|
|
||||||
this.zipCode = newValue.addressQuestions.zipCode;
|
|
||||||
this.isVehicleProtected = newValue.isVehicleProtected;
|
|
||||||
|
|
||||||
if (newValue.zipCode !== this.zipCode) {
|
|
||||||
if (
|
|
||||||
!(
|
|
||||||
this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|
|
||||||
|| this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
this.selectedAppointmentType = null;
|
|
||||||
}
|
|
||||||
this.selectedProvider = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isServiceableMobile() {
|
isServiceableMobile() {
|
||||||
if (this.isRecalibrationServiceableMobile !== null) {
|
if (this.isRecalibrationServiceableMobile !== null) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -338,22 +292,14 @@ export default {
|
||||||
|
|
||||||
return this.isGlassServiceableInshop;
|
return this.isGlassServiceableInshop;
|
||||||
},
|
},
|
||||||
isShopQuestionDisplayed() {
|
isInshop() {
|
||||||
return (
|
return (
|
||||||
this.selectedAppointmentType === 'Inshop'
|
this.selectedAppointmentType === 'Inshop'
|
||||||
|| this.selectedAppointmentType === 'Dropoff'
|
|| this.selectedAppointmentType === 'Dropoff'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
isAppointmentTypeDisplayed() {
|
isMobile() {
|
||||||
return this.zipCode && !this.displayNoShopsAlert;
|
return this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|
||||||
},
|
|
||||||
isMobileLocationDisplayed() {
|
|
||||||
return (
|
|
||||||
this.selectedAppointmentType
|
|
||||||
=== AppointmentTypeStrings.MOBILE
|
|
||||||
|| this.selectedAppointmentType
|
|
||||||
=== AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
requiresInshopRecalibration() {
|
requiresInshopRecalibration() {
|
||||||
// Specifically check for isRecalibrationServiceableMobile === false, not null or true.
|
// Specifically check for isRecalibrationServiceableMobile === false, not null or true.
|
||||||
|
|
@ -375,15 +321,17 @@ export default {
|
||||||
displayRecalibrationWarning() {
|
displayRecalibrationWarning() {
|
||||||
return this.requiresInshopRecalibration;
|
return this.requiresInshopRecalibration;
|
||||||
},
|
},
|
||||||
|
displayLowAvailabilityInshop() {
|
||||||
|
return this.availabilityRating === 'low' && this.isInshop;
|
||||||
|
},
|
||||||
displayServiceableInshopOnly() {
|
displayServiceableInshopOnly() {
|
||||||
return (
|
return !this.isServiceableMobile && this.isServiceableInshop && !this.displayRecalibrationWarning;
|
||||||
!this.displayRecalibrationWarning
|
|
||||||
&& this.isServiceableInshop
|
|
||||||
&& !this.isServiceableMobile
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
displayServiceableMobileOnly() {
|
displayServiceableMobileOnly() {
|
||||||
return this.isServiceableMobile && !this.isServiceableInshop;
|
return this.isServiceableMobile && this.recalibrationRequired && !this.isServiceableInshop;
|
||||||
|
},
|
||||||
|
displayMobileFeeDisclaimer() {
|
||||||
|
return this.mainStore.isNoComp || this.mainStore.isITAC;
|
||||||
},
|
},
|
||||||
navigateBackScenario() {
|
navigateBackScenario() {
|
||||||
const { isNoComp, isITAC } = useMainStore();
|
const { isNoComp, isITAC } = useMainStore();
|
||||||
|
|
@ -393,6 +341,38 @@ export default {
|
||||||
},
|
},
|
||||||
isBigTruck() {
|
isBigTruck() {
|
||||||
return this.mainStore.order.vehicle.isBigTruck;
|
return this.mainStore.order.vehicle.isBigTruck;
|
||||||
|
},
|
||||||
|
mobileZipPlaceholder() {
|
||||||
|
return this.getCmsContent('MobileZipPlaceHolderWidget', widgetFields.TEXT_BLOCK_WIDGET.TEXT);
|
||||||
|
},
|
||||||
|
militaryBaseWarningLinkText() {
|
||||||
|
return this.getCmsContent('AlertMilitaryBaseZipWidget', widgetFields.ALERT_WIDGET.HEADLINE_TEXT);
|
||||||
|
},
|
||||||
|
militaryBaseWarningText() {
|
||||||
|
return this.getCmsContent('AlertMilitaryBaseZipWidget', widgetFields.ALERT_WIDGET.BODY_TEXT);
|
||||||
|
},
|
||||||
|
appointmentQuestionText() {
|
||||||
|
return this.getCmsContent('WhereWouldYouLikeServiceWidget', widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT);
|
||||||
|
},
|
||||||
|
scheduleRecalText() {
|
||||||
|
if(this.requiresInshopRecalibration) {
|
||||||
|
return this.getCmsContent('WhereWouldYouLikeServiceWidget', widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT_2);
|
||||||
|
} else {
|
||||||
|
return this.getCmsContent('WhereWouldYouLikeServiceWidget', widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inShopOnlyCopy() {
|
||||||
|
let content = this.getCmsContent('AlertInshopOnlyWidget', widgetFields.ALERT_WIDGET.BODY_TEXT);
|
||||||
|
content = content.replace('{custom:city}', toTitleCase(this.city));
|
||||||
|
return content;
|
||||||
|
},
|
||||||
|
mobileOnlyCopy() {
|
||||||
|
let content = this.getCmsContent('AlertMobileOnlyWidget', widgetFields.ALERT_WIDGET.BODY_TEXT);
|
||||||
|
content = content.replace('{custom:city}', toTitleCase(this.city));
|
||||||
|
return content;
|
||||||
|
},
|
||||||
|
recalibrationRequired() {
|
||||||
|
return this.mainStore.hasRecalibrationPart;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -401,13 +381,10 @@ export default {
|
||||||
useMainStore().order.serviceLocation.zipCode !== null
|
useMainStore().order.serviceLocation.zipCode !== null
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
async reloadShopData(zipCode) {
|
|
||||||
await this.$refs[SHOP_QUESTION_REF_NAME].reloadShopData(zipCode);
|
|
||||||
},
|
|
||||||
async forwardButtonAction() {
|
async forwardButtonAction() {
|
||||||
let provider = this.selectedProvider;
|
let provider = this.selectedProvider;
|
||||||
this.mainStore.updateMobileFee(null);
|
this.mainStore.updateMobileFee(null);
|
||||||
if (this.isMobileLocationDisplayed) {
|
if (this.isMobile) {
|
||||||
if (this.mainStore.isNoComp || this.mainStore.isITAC) {
|
if (this.mainStore.isNoComp || this.mainStore.isITAC) {
|
||||||
this.mainStore.updateMobileFee(this.mobileFeePart);
|
this.mainStore.updateMobileFee(this.mobileFeePart);
|
||||||
}
|
}
|
||||||
|
|
@ -432,7 +409,6 @@ export default {
|
||||||
zipCode: this.zipCode,
|
zipCode: this.zipCode,
|
||||||
zipCodeCtu: this.zipCodeCtu,
|
zipCodeCtu: this.zipCodeCtu,
|
||||||
appointmentType: this.selectedAppointmentType,
|
appointmentType: this.selectedAppointmentType,
|
||||||
isVehicleProtected: this.isVehicleProtected,
|
|
||||||
provider
|
provider
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -466,9 +442,6 @@ export default {
|
||||||
|| useMainStore().order.customer.address.zipCode
|
|| useMainStore().order.customer.address.zipCode
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getIsVehicleProtectedFromStore() {
|
|
||||||
return useMainStore().order.serviceLocation.isVehicleProtected;
|
|
||||||
},
|
|
||||||
getSelectedAppointmentType() {
|
getSelectedAppointmentType() {
|
||||||
return useMainStore().order.serviceLocation.appointmentType;
|
return useMainStore().order.serviceLocation.appointmentType;
|
||||||
},
|
},
|
||||||
|
|
@ -482,12 +455,13 @@ export default {
|
||||||
zipCodeData,
|
zipCodeData,
|
||||||
serviceabilityDetails,
|
serviceabilityDetails,
|
||||||
mobileFeePart,
|
mobileFeePart,
|
||||||
mobileProviderNumber,
|
glassFees,
|
||||||
glassFees
|
providers
|
||||||
) {
|
) {
|
||||||
if (zipCodeData) {
|
if (zipCodeData) {
|
||||||
this.zipContainsMilitaryBase = zipCodeData.containsMilitaryBase;
|
this.zipContainsMilitaryBase = zipCodeData.containsMilitaryBase;
|
||||||
this.zipCodeCtu = zipCodeData.zipCodeCtu;
|
this.zipCodeCtu = zipCodeData.zipCodeCtu;
|
||||||
|
this.city = zipCodeData.city;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceabilityDetails) {
|
if (serviceabilityDetails) {
|
||||||
|
|
@ -498,8 +472,24 @@ export default {
|
||||||
this.mobileFeePart = mobileFeePart;
|
this.mobileFeePart = mobileFeePart;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mobileProviderNumber) {
|
if (providers) {
|
||||||
this.setMobileProviderNumber(mobileProviderNumber);
|
this.setMobileProviderNumber(providers.mobileProviderNumber);
|
||||||
|
let foundMatch = false;
|
||||||
|
if(this.selectedProvider && this.selectedProvider.providerNumber) {
|
||||||
|
const matchedProvider = providers.shopProviders.find(
|
||||||
|
(provider) => provider.providerNumber === this.selectedProvider.providerNumber
|
||||||
|
);
|
||||||
|
if (matchedProvider) {
|
||||||
|
foundMatch = true;
|
||||||
|
this.selectedProvider = matchedProvider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(providers.shopProviders.length > 0 && !foundMatch) {
|
||||||
|
this.selectedProvider = providers.shopProviders[0];
|
||||||
|
}
|
||||||
|
else if(providers.shopProviders.length === 0) {
|
||||||
|
this.selectedProvider = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (glassFees) {
|
if (glassFees) {
|
||||||
|
|
@ -521,13 +511,6 @@ export default {
|
||||||
setMobileProviderNumber(providerNumber) {
|
setMobileProviderNumber(providerNumber) {
|
||||||
this.mobileProviderNumber = providerNumber;
|
this.mobileProviderNumber = providerNumber;
|
||||||
},
|
},
|
||||||
resetMobileLocation() {
|
|
||||||
this.streetAddress = '';
|
|
||||||
this.streetAddress2 = '';
|
|
||||||
this.city = '';
|
|
||||||
|
|
||||||
this.isVehicleProtected = null;
|
|
||||||
},
|
|
||||||
setServiceabilityDetails(serviceabilityDetails) {
|
setServiceabilityDetails(serviceabilityDetails) {
|
||||||
this.isGlassServiceableInshop =
|
this.isGlassServiceableInshop =
|
||||||
serviceabilityDetails.isGlassServiceableInshop;
|
serviceabilityDetails.isGlassServiceableInshop;
|
||||||
|
|
@ -537,14 +520,94 @@ export default {
|
||||||
serviceabilityDetails.isGlassServiceableMobile;
|
serviceabilityDetails.isGlassServiceableMobile;
|
||||||
this.isRecalibrationServiceableMobile =
|
this.isRecalibrationServiceableMobile =
|
||||||
serviceabilityDetails.isRecalibrationServiceableMobile;
|
serviceabilityDetails.isRecalibrationServiceableMobile;
|
||||||
|
if (!this.isServiceableMobile) {
|
||||||
|
this.selectedAppointmentType = AppointmentTypeStrings.IN_SHOP;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async updateMobileZip() {
|
||||||
|
this.mobileZipError = '';
|
||||||
|
|
||||||
|
const zipCodeData = await getMobileZipCodeData(this.mobileZipCode);
|
||||||
|
|
||||||
|
if (!zipCodeData.isValid) {
|
||||||
|
this.mobileZipError = errorMessages.INVALID_ZIP;
|
||||||
|
} else if (!zipCodeData.isServiceable) {
|
||||||
|
this.mobileZipError = errorMessages.NO_SERVICE_IN_AREA(toTitleCase(zipCodeData.city));
|
||||||
|
} else {
|
||||||
|
this.city = zipCodeData.city;
|
||||||
|
this.zipCode = this.mobileZipCode;
|
||||||
|
this.setCtuForMobile(zipCodeData.zipCodeCtu);
|
||||||
|
this.setContainsMilitaryBase(zipCodeData.containsMilitaryBase);
|
||||||
|
|
||||||
|
const serviceabilityDetailsPromise = getServiceabilityDetails(this.mobileZipCode);
|
||||||
|
serviceabilityDetailsPromise.then((result) => {
|
||||||
|
const details = result.data;
|
||||||
|
if (details) {
|
||||||
|
this.setServiceabilityDetails(details);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const providersPromise = useMainStore().getProviders(this.mobileZipCode);
|
||||||
|
providersPromise.then((result) => {
|
||||||
|
const providers = result.data;
|
||||||
|
if (providers) {
|
||||||
|
this.setMobileProviderNumber(providers.mobileProviderNumber);
|
||||||
|
if(providers.shopProviders.length > 0) {
|
||||||
|
this.selectedProvider = providers.shopProviders[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.selectedProvider = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async handleInShopZipUpdated(newZip) {
|
||||||
|
this.zipCode = newZip;
|
||||||
|
const zipCodeData = await getZipCodeData(this.zipCode);
|
||||||
|
this.city = zipCodeData.city;
|
||||||
|
this.setCtuForMobile(zipCodeData.zipCodeCtu);
|
||||||
|
this.setContainsMilitaryBase(zipCodeData.containsMilitaryBase);
|
||||||
|
|
||||||
|
const serviceabilityDetailsPromise = getServiceabilityDetails(this.zipCode);
|
||||||
|
serviceabilityDetailsPromise.then((result) => {
|
||||||
|
const details = result.data;
|
||||||
|
if (details) {
|
||||||
|
this.setServiceabilityDetails(details);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
mobileZipCode(newZip) {
|
||||||
|
if(newZip !== '') {
|
||||||
|
this.updateMobileZip();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectedAppointmentType() {
|
||||||
|
this.mobileZipCode = '';
|
||||||
|
if(this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP && this.selectedProvider) {
|
||||||
|
const startDate = new Date();
|
||||||
|
const endDate = new Date();
|
||||||
|
endDate.setDate(startDate.getDate() + 6);
|
||||||
|
const formattedStartDate = startDate.toISOString().split('T')[0];
|
||||||
|
const formattedEndDate = endDate.toISOString().split('T')[0];
|
||||||
|
getAvailabilityRating(
|
||||||
|
formattedStartDate,
|
||||||
|
formattedEndDate,
|
||||||
|
AppointmentTypeStrings.IN_SHOP,
|
||||||
|
this.selectedProvider ? this.selectedProvider.providerNumber : null
|
||||||
|
).then((rating) => {
|
||||||
|
this.availabilityRating = rating;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss" scoped>
|
||||||
$page-side-padding: 1.5rem;
|
$page-side-padding: 1.5rem;
|
||||||
|
|
||||||
.iss-heritage-container-width {
|
.iss-heritage-container-width {
|
||||||
.service-location-container {
|
.service-location-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
@ -554,58 +617,41 @@ $page-side-padding: 1.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.question-text {
|
.service-location-container {
|
||||||
span {
|
.subheader {
|
||||||
line-height: 1.5rem;
|
margin: 1.25rem 0;
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
}
|
.form-test-error {
|
||||||
.list-card-content p {
|
font-weight: $font-weight-bold;
|
||||||
&:first-of-type {
|
|
||||||
line-height: 1.5rem;
|
|
||||||
}
|
}
|
||||||
&:not(:nth-of-type(1)) {
|
.appointment-type-question-text {
|
||||||
line-height: 1.25rem;
|
color: $black;
|
||||||
}
|
font-weight: 600;
|
||||||
}
|
margin-bottom: 1rem;
|
||||||
.choose-option {
|
font-size: 1rem;
|
||||||
.button-question > div {
|
line-height: 1.625rem;
|
||||||
&:first-of-type {
|
flex-direction: column;
|
||||||
margin-bottom: 0.95rem;
|
.recal-text {
|
||||||
line-height: 1.5rem;
|
margin-bottom: 1.25rem;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.mobile-service-zip-question {
|
||||||
.button-question {
|
:deep(.form-test-error span) {
|
||||||
.row.form-test-error {
|
font-weight: $font-weight-bold;
|
||||||
line-height: 1.5rem;
|
|
||||||
padding-left: 0rem !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.appointment-type-question {
|
|
||||||
.row {
|
|
||||||
margin-top: 0;
|
|
||||||
.col-12 {
|
|
||||||
margin: 0 0 .5rem 0;
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.question-text {
|
.expandable-link-container {
|
||||||
margin: 0;
|
margin-bottom: 1rem;
|
||||||
span {
|
|
||||||
padding: 1rem 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
.expandable-link {
|
||||||
.service-location-button-question {
|
font-style: italic;
|
||||||
.question-text {
|
font-weight: $font-weight-bold;
|
||||||
margin-top: 1.5rem;
|
text-decoration: none;
|
||||||
|
color: $heritage-blue-primary;
|
||||||
span {
|
line-height: 1.375rem;
|
||||||
text-align: center;
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import textLink from '@/ux-components/text-link/text-link.vue';
|
import textLink from '@/ux-components/text-link/text-link.vue';
|
||||||
import serviceZipQuestion from '@/layouts/service-location/service-zip-modal-question/service-zip-question/service-zip-question.vue';
|
import serviceZipQuestion from '@/layouts/service-location/service-zip-question/service-zip-question.vue';
|
||||||
import modal from '@/digital-components/modal/modal.vue';
|
import modal from '@/digital-components/modal/modal.vue';
|
||||||
import alert from '@/ux-components/alert/alert.vue';
|
import alert from '@/ux-components/alert/alert.vue';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
<template>
|
|
||||||
<textboxQuestion
|
|
||||||
ref="zipInputTextQuestion"
|
|
||||||
v-model="value"
|
|
||||||
:cmsWidgetName="cmsWidgetName"
|
|
||||||
inputId="serviceZipCode"
|
|
||||||
questionAlignment="center"
|
|
||||||
cornerStyle="rounded"
|
|
||||||
mask="#####"
|
|
||||||
:displayQuestionText="false"
|
|
||||||
isRequired
|
|
||||||
validationRules="zip-required|zip-format" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue';
|
|
||||||
|
|
||||||
// Supporting Files
|
|
||||||
import { defineRule } from 'vee-validate';
|
|
||||||
import { required, regex } from '@/helpers/validation-rules';
|
|
||||||
import errorMessages from '@/constants/error-messages';
|
|
||||||
|
|
||||||
// Define Validation Rules
|
|
||||||
defineRule('zip-required', required(errorMessages.SERVICE_ZIP_REQUIRED));
|
|
||||||
defineRule('zip-format', regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.SERVICE_ZIP_FORMAT));
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'service-zip-question',
|
|
||||||
components: {
|
|
||||||
textboxQuestion
|
|
||||||
},
|
|
||||||
// TODO: Correct prop def.
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
serviceZipCode: String
|
|
||||||
},
|
|
||||||
cmsWidgetName: String
|
|
||||||
},
|
|
||||||
emits: ['update:modelValue'],
|
|
||||||
computed: {
|
|
||||||
value: {
|
|
||||||
get() {
|
|
||||||
return this.modelValue;
|
|
||||||
},
|
|
||||||
set(newValue) {
|
|
||||||
this.$emit('update:modelValue', newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import serviceZipQuestion from '@/layouts/service-location/service-zip-modal-question/service-zip-question/service-zip-question.vue';
|
import serviceZipQuestion from '@/layouts/service-location/service-zip-question/service-zip-question.vue';
|
||||||
|
|
||||||
describe('service-zip-question.vue', () => {
|
describe('service-zip-question.vue', () => {
|
||||||
it('Should get the modelValue', async () => {
|
it('Should get the modelValue', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const text = 'test';
|
const text = '12345';
|
||||||
const wrapper = shallowMount(serviceZipQuestion, {
|
const wrapper = shallowMount(serviceZipQuestion, {
|
||||||
props: {
|
props: {
|
||||||
modelValue: text
|
modelValue: text
|
||||||
|
|
@ -13,16 +13,16 @@ describe('service-zip-question.vue', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const modelValueText = wrapper.vm.value;
|
const modelValueText = wrapper.vm.internalZipcode;
|
||||||
wrapper.vm.value = 'test also';
|
wrapper.vm.internalZipcode = '55555';
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(modelValueText).toEqual('test');
|
expect(modelValueText).toEqual('12345');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should emit to set value', async () => {
|
it('Should emit to set value', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const text = 'test';
|
const text = '12345';
|
||||||
const wrapper = shallowMount(serviceZipQuestion, {
|
const wrapper = shallowMount(serviceZipQuestion, {
|
||||||
props: {
|
props: {
|
||||||
modelValue: text
|
modelValue: text
|
||||||
|
|
@ -31,9 +31,10 @@ describe('service-zip-question.vue', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
wrapper.vm.value = 'test also';
|
wrapper.vm.internalZipcode = '55555';
|
||||||
|
wrapper.vm.updateModelValue();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(wrapper.emitted('update:modelValue')).toEqual([['test also']]);
|
expect(wrapper.emitted('update:modelValue')).toEqual([['55555']]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
<template>
|
||||||
|
<textboxQuestion
|
||||||
|
ref="zipInputTextQuestion"
|
||||||
|
v-model="internalZipcode"
|
||||||
|
:cmsWidgetName="cmsWidgetName"
|
||||||
|
inputId="serviceZipCode"
|
||||||
|
cornerStyle="rounded"
|
||||||
|
mask="#####"
|
||||||
|
isRequired
|
||||||
|
:hasError="hasError"
|
||||||
|
@clickEvent="updateModelValue"
|
||||||
|
:validationRules="`zip-required|${cmsWidgetName}-zip-format`" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue';
|
||||||
|
|
||||||
|
// Supporting Files
|
||||||
|
import { defineRule } from 'vee-validate';
|
||||||
|
import { required, regex } from '@/helpers/validation-rules';
|
||||||
|
import errorMessages from '@/constants/error-messages';
|
||||||
|
|
||||||
|
// Define Validation Rules
|
||||||
|
defineRule('zip-required', required(errorMessages.SERVICE_ZIP_REQUIRED));
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'service-zip-question',
|
||||||
|
components: {
|
||||||
|
textboxQuestion
|
||||||
|
},
|
||||||
|
// TODO: Correct prop def.
|
||||||
|
props: {
|
||||||
|
modelValue: String,
|
||||||
|
cmsWidgetName: String,
|
||||||
|
serviceZipFormatErrorMessage: {
|
||||||
|
type: String,
|
||||||
|
default: errorMessages.SERVICE_ZIP_FORMAT
|
||||||
|
},
|
||||||
|
hasError: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
internalZipcode: this.modelValue
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
methods: {
|
||||||
|
updateModelValue() {
|
||||||
|
this.$emit('update:modelValue', this.internalZipcode);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue(newValue) {
|
||||||
|
this.internalZipcode = newValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
defineRule(`${this.cmsWidgetName}-zip-format`, regex(/^\d{5}$/, this.serviceZipFormatErrorMessage));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.textbox-question.has-success {
|
||||||
|
:deep(input.form-control) {
|
||||||
|
border: $border-input;
|
||||||
|
&:focus {
|
||||||
|
border: $border-input-focus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
411
src/layouts/service-location/shop-address/shop-address.vue
Normal file
411
src/layouts/service-location/shop-address/shop-address.vue
Normal file
|
|
@ -0,0 +1,411 @@
|
||||||
|
<template>
|
||||||
|
<transition
|
||||||
|
name="fade"
|
||||||
|
mode="out-in">
|
||||||
|
<div
|
||||||
|
class="shop-address"
|
||||||
|
aria-live="polite">
|
||||||
|
<div class="address-header">{{ questionText }}</div>
|
||||||
|
<div
|
||||||
|
v-if="modelValue"
|
||||||
|
class="current-address">
|
||||||
|
<div class="address-line">
|
||||||
|
<span class="shop-name">{{ displayedShopName }}</span>
|
||||||
|
<span class="shop-distance">{{ modelValue.distanceInMiles?.toFixed(2) }} mi</span>
|
||||||
|
</div>
|
||||||
|
<div class="address-line">{{ getProviderAddress(modelValue) }}</div>
|
||||||
|
<div class="address-line">
|
||||||
|
{{ getProviderCityZipState(modelValue) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="no-shops-found">
|
||||||
|
{{ `No shops found within 100 miles of ${internalZipcode}` }}
|
||||||
|
</div>
|
||||||
|
<buttonMain
|
||||||
|
class="more-shops-button"
|
||||||
|
:buttonText="showMoreShopsLinkText"
|
||||||
|
:variant="'primary'"
|
||||||
|
@clickEvent="openModal" />
|
||||||
|
<alert
|
||||||
|
class="alert-shop-distance"
|
||||||
|
v-if="modelValue?.distanceInMiles > 30"
|
||||||
|
ref="alertShopDistance"
|
||||||
|
cmsWidgetName="AlertShopDistanceWidget"
|
||||||
|
alertClass="alert-warning" />
|
||||||
|
<modal
|
||||||
|
:ref="modalName"
|
||||||
|
:headerText="modalHeaderText"
|
||||||
|
:onModalOpenedCallback="onModalOpened"
|
||||||
|
:footerButtonText="modalFooterText"
|
||||||
|
:modalPosition="modalPositions.center"
|
||||||
|
:allowInvalidSubmit="true"
|
||||||
|
@footerButtonEvent="updateSelectedProvider">
|
||||||
|
<googleMap
|
||||||
|
id="map"
|
||||||
|
class="mb-4"
|
||||||
|
:markers="providerAddresses"
|
||||||
|
:zipCode="internalZipcode" />
|
||||||
|
<dropdownQuestion
|
||||||
|
inputId="searchRadiusDropdown"
|
||||||
|
ref="searchRadiusQuestion"
|
||||||
|
v-model="searchRadiusInMiles"
|
||||||
|
:cmsWidgetName="searchRadiusQuestionWidgetName"
|
||||||
|
:variant="dropdownVariants.compact"
|
||||||
|
:options="searchRadiusOptions" />
|
||||||
|
<buttonQuestion
|
||||||
|
ref="shopListButtonQuestion"
|
||||||
|
v-model="internalProviderNumber"
|
||||||
|
buttonTypeString="shopListButton"
|
||||||
|
:buttonTypeObject="shopListButton"
|
||||||
|
class="radioQuestion"
|
||||||
|
:answers="nearbyShopsData"
|
||||||
|
groupName="chooseShop"
|
||||||
|
textPosition="text-start"
|
||||||
|
isRequired
|
||||||
|
validationRules="option-required"
|
||||||
|
:additionalButtonData="additionalButtonData" />
|
||||||
|
<span class="service-zip-prompt">
|
||||||
|
{{ serviceZipPrompt }}
|
||||||
|
</span>
|
||||||
|
<serviceZipQuestion
|
||||||
|
v-if="renderServiceZip"
|
||||||
|
:ref="SERVICE_ZIP_QUESTION_REF_NAME"
|
||||||
|
v-model="internalZipcode"
|
||||||
|
customInputId="serviceZipCode"
|
||||||
|
:cmsWidgetName="textboxQuestionWidgetName" />
|
||||||
|
<div
|
||||||
|
v-if="errorMessage"
|
||||||
|
ref="errorMessageDiv"
|
||||||
|
class="row my-1 form-test-error">
|
||||||
|
<span
|
||||||
|
class="d-inline-flex mt-0"
|
||||||
|
role="alert">{{ errorMessage }}</span>
|
||||||
|
</div>
|
||||||
|
</modal>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Components
|
||||||
|
import alert from '@/ux-components/alert/alert.vue';
|
||||||
|
import buttonMain from '@/ux-components/button-main/button-main.vue';
|
||||||
|
import modal from '@/digital-components/modal/modal.vue';
|
||||||
|
import serviceZipQuestion from '@/layouts/service-location/service-zip-question/service-zip-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 dropdownQuestion from '@/digital-components/dropdown-question/dropdown-question.vue';
|
||||||
|
import googleMap from '@/iss-components/google-map/google-map.vue';
|
||||||
|
|
||||||
|
// Supporting files
|
||||||
|
import baseMixin from '@/mixins/base-mixin.js';
|
||||||
|
import { toTitleCase } from '@/helpers/text-helper.js';
|
||||||
|
import { markRaw } from 'vue';
|
||||||
|
import { getAvailabilityRating } from '@/helpers/service-location-helper';
|
||||||
|
import { useMainStore } from '@/store';
|
||||||
|
import widgetFields from '@/constants/cms-widget-fields';
|
||||||
|
import { dropdownVariants, modalPositions } from '@/constants/component-variants';
|
||||||
|
import errorMessages from '@/constants/error-messages';
|
||||||
|
import { defineRule } from 'vee-validate';
|
||||||
|
import { required } from '@/helpers/validation-rules';
|
||||||
|
|
||||||
|
defineRule('option-required', required(errorMessages.PROVIDER_REQUIRED));
|
||||||
|
|
||||||
|
const SERVICE_ZIP_QUESTION_REF_NAME = 'serviceZipCodeQuestion';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'shop-address',
|
||||||
|
components: {
|
||||||
|
alert,
|
||||||
|
buttonMain,
|
||||||
|
modal,
|
||||||
|
serviceZipQuestion,
|
||||||
|
buttonQuestion,
|
||||||
|
dropdownQuestion,
|
||||||
|
googleMap
|
||||||
|
},
|
||||||
|
mixins: [baseMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null
|
||||||
|
},
|
||||||
|
serviceZipcode: String,
|
||||||
|
cmsWidgetName: String,
|
||||||
|
validationRules: String,
|
||||||
|
modalWidgetName: String,
|
||||||
|
selectedAppointmentType: String,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
toTitleCase,
|
||||||
|
serviceZipCodeTextInputId: '',
|
||||||
|
internalZipcode: this.serviceZipcode,
|
||||||
|
internalProviderNumber: this.modelValue?.providerNumber,
|
||||||
|
searchRadiusQuestionWidgetName: 'SearchRadiusQuestionWidget',
|
||||||
|
textboxQuestionWidgetName: 'ChangeLocationZipQuestionWidget',
|
||||||
|
shopListButton: markRaw(shopListButton),
|
||||||
|
searchRadiusInMiles: "25",
|
||||||
|
nearbyShops: [],
|
||||||
|
modalPositions,
|
||||||
|
dropdownVariants,
|
||||||
|
renderServiceZip: true,
|
||||||
|
errorMessage: '',
|
||||||
|
openingModal: false,
|
||||||
|
SERVICE_ZIP_QUESTION_REF_NAME
|
||||||
|
};
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue', 'zip-updated'],
|
||||||
|
computed: {
|
||||||
|
questionText() {
|
||||||
|
return this.getCmsContent(this.cmsWidgetName, widgetFields.INPUT_QUESTION_WIDGET.QUESTION_TEXT);
|
||||||
|
},
|
||||||
|
showMoreShopsLinkText() {
|
||||||
|
return this.getCmsContent('ShowMoreShopsLinkWidget', widgetFields.TEXT_BLOCK_WIDGET.TEXT);
|
||||||
|
},
|
||||||
|
displayedShopName() {
|
||||||
|
if(this.modelValue.isSafeliteShop) {
|
||||||
|
return this.getCmsContent('SafeliteShopNameWidget', widgetFields.TEXT_BLOCK_WIDGET.TEXT);
|
||||||
|
}
|
||||||
|
return toTitleCase(this.modelValue.companyName);
|
||||||
|
},
|
||||||
|
modalHeaderText() {
|
||||||
|
return this.getCmsContent(this.modalWidgetName, widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT).replaceAll('{custom:serviceZipcode}', this.internalZipcode);
|
||||||
|
},
|
||||||
|
modalFooterText() {
|
||||||
|
return this.getCmsContent(this.modalWidgetName, widgetFields.CONTENT_GROUP_WIDGET.FOOTER_TEXT);
|
||||||
|
},
|
||||||
|
modalName() {
|
||||||
|
return this.modalWidgetName;
|
||||||
|
},
|
||||||
|
additionalButtonData() {
|
||||||
|
const startDate = new Date();
|
||||||
|
const endDate = new Date();
|
||||||
|
endDate.setDate(startDate.getDate() + 6);
|
||||||
|
|
||||||
|
const formattedStartDate = startDate.toISOString().split('T')[0];
|
||||||
|
const formattedEndDate = endDate.toISOString().split('T')[0];
|
||||||
|
|
||||||
|
return {
|
||||||
|
displayAvailabilityIndicators: true,
|
||||||
|
availabilityRatingCallback: getAvailabilityRating,
|
||||||
|
startDate: formattedStartDate,
|
||||||
|
endDate: formattedEndDate,
|
||||||
|
shopAppointmentType: "InshopOrDropoff"
|
||||||
|
};
|
||||||
|
},
|
||||||
|
serviceZipPrompt() {
|
||||||
|
return this.getCmsContent(this.modalWidgetName, widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT_2);
|
||||||
|
},
|
||||||
|
nearbyShopsData() {
|
||||||
|
return this.nearbyShops.map((shopProvider) => {
|
||||||
|
const city = toTitleCase(shopProvider.address.city);
|
||||||
|
const distanceInMiles = shopProvider.distanceInMiles.toFixed(2);
|
||||||
|
|
||||||
|
return {
|
||||||
|
buttonLabel: city,
|
||||||
|
buttonLabelSubCopy: `${distanceInMiles} mi`,
|
||||||
|
buttonBodyCopy: this.getFullProviderAddress(shopProvider),
|
||||||
|
value: shopProvider.providerNumber
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
searchRadiusArray() {
|
||||||
|
const options = this.getCmsContent(this.searchRadiusQuestionWidgetName, widgetFields.INPUT_QUESTION_WIDGET.ANSWERS);
|
||||||
|
return options;
|
||||||
|
},
|
||||||
|
searchRadiusOptions() {
|
||||||
|
const optionsObj = {};
|
||||||
|
if(this.searchRadiusArray) {
|
||||||
|
this.searchRadiusArray.forEach(option => {
|
||||||
|
optionsObj[option.Name] = option.Text;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return optionsObj;
|
||||||
|
},
|
||||||
|
providerAddresses() {
|
||||||
|
return this.nearbyShops?.map((provider) => ({
|
||||||
|
title: toTitleCase(provider.companyName),
|
||||||
|
fullAddress: this.getFullProviderAddress(provider),
|
||||||
|
addressLines: [this.getProviderAddress(provider), this.getProviderCityZipState(provider)]
|
||||||
|
})) ?? [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
async internalZipcode() {
|
||||||
|
if(this.openingModal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const shopsUpdated = await this.updateShops();
|
||||||
|
if (shopsUpdated) {
|
||||||
|
await this.$nextTick();
|
||||||
|
this.$refs[SERVICE_ZIP_QUESTION_REF_NAME].internalZipcode = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async searchRadiusInMiles() {
|
||||||
|
if(this.openingModal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.updateShops();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async updateShops(autoExpand = false) {
|
||||||
|
this.errorMessage = '';
|
||||||
|
let result = await this.getNearbyShops(this.searchRadiusInMiles);
|
||||||
|
let currentSearchIndex = this.searchRadiusArray.findIndex(option => option.Name === this.searchRadiusInMiles);
|
||||||
|
while (autoExpand && result.length === 0 && currentSearchIndex < this.searchRadiusArray.length - 1) {
|
||||||
|
const newSearchRadius = this.searchRadiusArray[currentSearchIndex + 1].Name;
|
||||||
|
result = await this.getNearbyShops(newSearchRadius);
|
||||||
|
currentSearchIndex++;
|
||||||
|
}
|
||||||
|
if (result.length === 0) {
|
||||||
|
this.errorMessage = errorMessages.ZIP_CODE_NOT_SERVICED_FOR_VEHICLE;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!result.find(shop => shop.providerNumber === this.internalProviderNumber)) {
|
||||||
|
this.internalProviderNumber = '';
|
||||||
|
await this.$nextTick();
|
||||||
|
}
|
||||||
|
this.nearbyShops = result;
|
||||||
|
this.searchRadiusInMiles = this.searchRadiusArray[currentSearchIndex].Name;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openModal() {
|
||||||
|
this.$refs[this.modalName].openModal();
|
||||||
|
},
|
||||||
|
closeModal() {
|
||||||
|
this.$refs[this.modalName].closeModal();
|
||||||
|
},
|
||||||
|
onModalOpened() {
|
||||||
|
this.openingModal = true;
|
||||||
|
this.internalZipcode = this.serviceZipcode;
|
||||||
|
this.searchRadiusInMiles = "25";
|
||||||
|
this.nearbyShops = [];
|
||||||
|
this.updateShops(true).then(() => {
|
||||||
|
this.internalProviderNumber = this.nearbyShops[0]?.providerNumber;
|
||||||
|
}).finally(() => {
|
||||||
|
this.$nextTick().then(() => {
|
||||||
|
this.openingModal = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.forceServiceZipRerender();
|
||||||
|
},
|
||||||
|
async updateSelectedProvider() {
|
||||||
|
const selectedShop = this.nearbyShops.find(shop => shop.providerNumber === this.internalProviderNumber);
|
||||||
|
if (selectedShop) {
|
||||||
|
this.$emit('update:modelValue', selectedShop);
|
||||||
|
this.$emit('zip-updated', this.internalZipcode);
|
||||||
|
this.closeModal();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getNearbyShops(radiusInMiles) {
|
||||||
|
const result = await useMainStore().getProviders(this.internalZipcode, radiusInMiles);
|
||||||
|
return result.data.shopProviders;
|
||||||
|
},
|
||||||
|
getFullProviderAddress(provider) {
|
||||||
|
const addressLine1 = this.getProviderAddress(provider);
|
||||||
|
const addressLine2 = this.getProviderCityZipState(provider);
|
||||||
|
const joinString =
|
||||||
|
addressLine1.length > 0 && addressLine2.length > 0 ? ', ' : '';
|
||||||
|
return [addressLine1, addressLine2].join(joinString);
|
||||||
|
},
|
||||||
|
getProviderAddress(provider) {
|
||||||
|
return toTitleCase(provider?.address?.streetAddress);
|
||||||
|
},
|
||||||
|
getProviderCityZipState(provider) {
|
||||||
|
const city = toTitleCase(provider?.address?.city);
|
||||||
|
const state = provider?.address?.state ?? '';
|
||||||
|
const zipCode = provider?.address?.zipCode ?? '';
|
||||||
|
let addressLine2 = '';
|
||||||
|
if (city) {
|
||||||
|
addressLine2 += city;
|
||||||
|
if (state || zipCode) {
|
||||||
|
addressLine2 += state ? ', ' : ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state) {
|
||||||
|
addressLine2 += state;
|
||||||
|
addressLine2 += zipCode ? ' ' : '';
|
||||||
|
}
|
||||||
|
if (zipCode) {
|
||||||
|
addressLine2 += zipCode;
|
||||||
|
}
|
||||||
|
return addressLine2;
|
||||||
|
},
|
||||||
|
forceServiceZipRerender() {
|
||||||
|
this.renderServiceZip = false;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.renderServiceZip = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "@/styles/ux-variables-svg-strings.scss";
|
||||||
|
.shop-address {
|
||||||
|
.address-header {
|
||||||
|
color: $black;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.625rem;
|
||||||
|
}
|
||||||
|
.address-line {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.shop-distance {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
.current-address {
|
||||||
|
margin: .625rem 0;
|
||||||
|
}
|
||||||
|
.no-shops-found {
|
||||||
|
font-size: .75rem;
|
||||||
|
margin: .75rem 0rem;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
line-height: 1.375rem;
|
||||||
|
}
|
||||||
|
.more-shops-button {
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
.alert-shop-distance {
|
||||||
|
margin-top: 2.5rem;
|
||||||
|
}
|
||||||
|
:deep(.modal-title) {
|
||||||
|
font-weight: 400;
|
||||||
|
display: block;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
.radioQuestion {
|
||||||
|
margin-bottom: .625rem;
|
||||||
|
}
|
||||||
|
.service-zip-prompt {
|
||||||
|
color: $black;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
:deep(#map) {
|
||||||
|
height: 27rem;
|
||||||
|
}
|
||||||
|
:deep(.textbox-question) {
|
||||||
|
&.has-error {
|
||||||
|
input.form-control {
|
||||||
|
border: $border-input;
|
||||||
|
&:focus {
|
||||||
|
border: $border-input-focus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.form-label {
|
||||||
|
font-weight: $font-weight-normal;
|
||||||
|
color: $darker-gray;
|
||||||
|
}
|
||||||
|
.btn-primary {
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,294 +0,0 @@
|
||||||
<template>
|
|
||||||
<transition
|
|
||||||
name="fade"
|
|
||||||
mode="out-in">
|
|
||||||
<div
|
|
||||||
v-if="isDisplayed"
|
|
||||||
class="shop-question"
|
|
||||||
aria-live="polite">
|
|
||||||
<alert
|
|
||||||
v-if="displayDropoffInformation"
|
|
||||||
ref="alertDropoffInformation"
|
|
||||||
class="mb-4 drop-off-alert"
|
|
||||||
cmsWidgetName="AlertDropoffInformationWidget"
|
|
||||||
alertClass="alert-info"
|
|
||||||
:isDismissible="false" />
|
|
||||||
<buttonQuestion
|
|
||||||
ref="buttonQuestion"
|
|
||||||
v-model="selectedProviderNumber"
|
|
||||||
buttonTypeString="shopListButton"
|
|
||||||
:buttonTypeObject="shopListButton"
|
|
||||||
class="radioQuestion"
|
|
||||||
:questionText="questionText"
|
|
||||||
:answers="answers"
|
|
||||||
groupName="chooseShop"
|
|
||||||
textPosition="text-start"
|
|
||||||
isRequired
|
|
||||||
validationRules="option-required"
|
|
||||||
:additionalButtonData="additionalButtonData" />
|
|
||||||
<textLink
|
|
||||||
v-show="displaySeeMoreLocationsLink"
|
|
||||||
id="showMoreShopsId"
|
|
||||||
ref="showMoreShopsLink"
|
|
||||||
class="show-more-shops-link"
|
|
||||||
cmsWidgetName="ShowMoreShopsLinkWidget"
|
|
||||||
linkType="text"
|
|
||||||
:text="showMoreShopsLinkText"
|
|
||||||
href="#!"
|
|
||||||
:aria-label="showMoreShopsLinkText"
|
|
||||||
@clickEvent="getNextShopsFromList" />
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Components
|
|
||||||
import alert from '@/ux-components/alert/alert.vue';
|
|
||||||
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
|
|
||||||
import textLink from '@/ux-components/text-link/text-link.vue';
|
|
||||||
|
|
||||||
// Supporting files
|
|
||||||
import { AppointmentTypeStrings } from '@/constants/schedule-constants.js';
|
|
||||||
import baseMixin from '@/mixins/base-mixin.js';
|
|
||||||
import { defineRule } from 'vee-validate';
|
|
||||||
import { required } from '@/helpers/validation-rules';
|
|
||||||
import errorMessages from '@/constants/error-messages';
|
|
||||||
import { useMainStore } from '@/store/index.js';
|
|
||||||
import { markRaw, nextTick } from 'vue';
|
|
||||||
import { getAvailabilityRating } from '@/helpers/service-location-helper';
|
|
||||||
import shopListButton from '@/iss-components/shop-list-button/shop-list-button.vue';
|
|
||||||
|
|
||||||
defineRule('option-required', required(errorMessages.OPTION_REQUIRED));
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'shop-question',
|
|
||||||
components: {
|
|
||||||
alert,
|
|
||||||
buttonQuestion,
|
|
||||||
textLink
|
|
||||||
},
|
|
||||||
mixins: [baseMixin],
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: Object,
|
|
||||||
default: () => null
|
|
||||||
},
|
|
||||||
selectedAppointmentType: String,
|
|
||||||
cmsWidgetName: String,
|
|
||||||
validationRules: String,
|
|
||||||
isDisplayed: Boolean
|
|
||||||
},
|
|
||||||
emits: ['update:modelValue', 'updatedMobileProviderNumber'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
shopProviders: [],
|
|
||||||
shopListButton: markRaw(shopListButton),
|
|
||||||
answers: [],
|
|
||||||
shopIndex: 0,
|
|
||||||
displaySeeMoreLocationsLink: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
questionText() {
|
|
||||||
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
|
|
||||||
},
|
|
||||||
selectedValue: {
|
|
||||||
get() {
|
|
||||||
return this.modelValue;
|
|
||||||
},
|
|
||||||
set(newValue) {
|
|
||||||
this.$emit('update:modelValue', newValue);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectedProviderNumber: {
|
|
||||||
get() {
|
|
||||||
return this.selectedValue?.providerNumber;
|
|
||||||
},
|
|
||||||
set(newValue) {
|
|
||||||
// Button Question only supports Number, or String data types so we must get the full object to emit
|
|
||||||
this.selectedValue = this.getSelectedProviderObject(newValue);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
displayDropoffInformation() {
|
|
||||||
return this.selectedAppointmentType === 'Dropoff';
|
|
||||||
},
|
|
||||||
showMoreShopsLinkText() {
|
|
||||||
return this.getCmsContent('ShowMoreShopsLinkWidget', 'Text');
|
|
||||||
},
|
|
||||||
additionalButtonData() {
|
|
||||||
const startDate = new Date();
|
|
||||||
const endDate = new Date();
|
|
||||||
endDate.setDate(startDate.getDate() + 6);
|
|
||||||
|
|
||||||
const formattedStartDate = startDate.toISOString().split('T')[0];
|
|
||||||
const formattedEndDate = endDate.toISOString().split('T')[0];
|
|
||||||
|
|
||||||
return {
|
|
||||||
displayAvailabilityIndicators: true,
|
|
||||||
availabilityRatingCallback: getAvailabilityRating,
|
|
||||||
startDate: formattedStartDate,
|
|
||||||
endDate: formattedEndDate,
|
|
||||||
shopAppointmentType: this.selectedAppointmentType
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
selectedAppointmentType: {
|
|
||||||
async handler(newValue) {
|
|
||||||
this.resetAnswers();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
this.selectedProviderNumber = null;
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
if (newValue !== AppointmentTypeStrings.MOBILE && newValue !== AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
|
|
||||||
this.getNextShopsFromList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
shopProviders: {
|
|
||||||
async handler(newValue) {
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
if (this.selectedAppointmentType) {
|
|
||||||
const selectedShopIndex = this.getSelectedProviderIndex(
|
|
||||||
newValue,
|
|
||||||
this.selectedProviderNumber
|
|
||||||
);
|
|
||||||
|
|
||||||
if (selectedShopIndex >= 3) {
|
|
||||||
await this.getNextShopsFromList(selectedShopIndex + 1);
|
|
||||||
} else {
|
|
||||||
await this.getNextShopsFromList();
|
|
||||||
await nextTick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
loadInitialData(serviceZipCode) {
|
|
||||||
return this.loadData(serviceZipCode);
|
|
||||||
},
|
|
||||||
loadData(serviceZipCode) {
|
|
||||||
return useMainStore().getProviders(serviceZipCode);
|
|
||||||
},
|
|
||||||
initializeComponent(shopQuestionInitialData) {
|
|
||||||
this.shopProviders = shopQuestionInitialData.shopProviders;
|
|
||||||
},
|
|
||||||
async getNextShopsFromList(numberToGet = 3) {
|
|
||||||
const shopIterator = (array, n) => {
|
|
||||||
const l = array.length;
|
|
||||||
return () => {
|
|
||||||
const end = this.shopIndex + n;
|
|
||||||
const part = array.slice(this.shopIndex, end);
|
|
||||||
this.shopIndex = end < l ? end : this.shopProviders.length;
|
|
||||||
return part;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const toTitleCase = (str) => str.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());
|
|
||||||
|
|
||||||
const nextShop = shopIterator(this.shopProviders, numberToGet);
|
|
||||||
|
|
||||||
// Map API result data
|
|
||||||
const mappedData = nextShop().map((shopProvider) => {
|
|
||||||
const streetAddress = toTitleCase(shopProvider.address.streetAddress);
|
|
||||||
const city = toTitleCase(shopProvider.address.city);
|
|
||||||
const { state } = shopProvider.address;
|
|
||||||
const { zipCode } = shopProvider.address;
|
|
||||||
const distanceInMiles = Math.round(shopProvider.distanceInMiles * 2) / 2;
|
|
||||||
|
|
||||||
return {
|
|
||||||
buttonLabel: city,
|
|
||||||
buttonLabelSubCopy: `${distanceInMiles} mi`,
|
|
||||||
buttonBodyCopy: `${streetAddress}, ${city}, ${state} ${zipCode}`,
|
|
||||||
value: shopProvider.providerNumber
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.answers.length === 0) {
|
|
||||||
this.answers = mappedData;
|
|
||||||
} else {
|
|
||||||
mappedData.forEach((shop) => {
|
|
||||||
this.answers.push(shop);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
if (this.shopIndex === this.shopProviders.length) {
|
|
||||||
this.displaySeeMoreLocationsLink = false;
|
|
||||||
} else {
|
|
||||||
this.displaySeeMoreLocationsLink = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
this.scrollToPageBottom();
|
|
||||||
},
|
|
||||||
resetAnswers() {
|
|
||||||
this.answers = [];
|
|
||||||
this.shopIndex = 0;
|
|
||||||
},
|
|
||||||
async reloadShopData(serviceZipCode) {
|
|
||||||
const result = await this.loadData(serviceZipCode);
|
|
||||||
this.initializeComponent(result.data);
|
|
||||||
|
|
||||||
if (result.data?.mobileProviderNumber
|
|
||||||
&& (this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|
|
||||||
|| this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP)) {
|
|
||||||
this.$emit('updatedMobileProviderNumber', result.data.mobileProviderNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.resetAnswers();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
await this.getNextShopsFromList();
|
|
||||||
},
|
|
||||||
getSelectedProviderObject(providerNumber) {
|
|
||||||
const provider = this.shopProviders?.find((p) => p.providerNumber === providerNumber);
|
|
||||||
return provider;
|
|
||||||
},
|
|
||||||
getSelectedProviderIndex(providers, selectedProviderNumber) {
|
|
||||||
const index = providers.findIndex((p) => p.providerNumber === selectedProviderNumber);
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
@import "@/styles/ux-variables-svg-strings.scss";
|
|
||||||
|
|
||||||
.shop-question {
|
|
||||||
margin-top: 1rem;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.button-question {
|
|
||||||
.question-text {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.drop-off-alert {
|
|
||||||
background-image: url($svg-drop-off-alert);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 0.75rem;
|
|
||||||
background-position: 0.5rem 0.75rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
padding: 0.5rem 0.5rem 0.5rem 1.5rem !important;
|
|
||||||
gap: 0.25rem;
|
|
||||||
|
|
||||||
.alert-heading {
|
|
||||||
text-align: left;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
line-height: 1.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1012,14 +1012,13 @@ export const useMainStore = defineStore({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getProviders(serviceZipCode) {
|
getProviders(serviceZipCode, shopRadiusInMiles = 100) {
|
||||||
const { carId, isBigTruck } = this.order.vehicle;
|
const { carId, isBigTruck } = this.order.vehicle;
|
||||||
const damageType = this.damage.isRepair ? 'Repair' : 'Replace';
|
const damageType = this.damage.isRepair ? 'Repair' : 'Replace';
|
||||||
const { parentAccountNumber } = this.order;
|
const { parentAccountNumber } = this.order;
|
||||||
const partsWithRecal = getTopLevelGlassPartsWithRecal(this.order.lineItems.glassParts);
|
const partsWithRecal = getTopLevelGlassPartsWithRecal(this.order.lineItems.glassParts);
|
||||||
const windshieldPartWithRecal = partsWithRecal?.length > 0 ? partsWithRecal[0] : null;
|
const windshieldPartWithRecal = partsWithRecal?.length > 0 ? partsWithRecal[0] : null;
|
||||||
const safeliteOnly = true;
|
const safeliteOnly = true;
|
||||||
const shopRadiusInMiles = 100;
|
|
||||||
|
|
||||||
let url = `${endpoints.GetProviders.url}/${serviceZipCode}/${damageType}/${shopRadiusInMiles}/${parentAccountNumber}/${safeliteOnly}/${carId}`;
|
let url = `${endpoints.GetProviders.url}/${serviceZipCode}/${damageType}/${shopRadiusInMiles}/${parentAccountNumber}/${safeliteOnly}/${carId}`;
|
||||||
if (windshieldPartWithRecal) {
|
if (windshieldPartWithRecal) {
|
||||||
|
|
|
||||||
|
|
@ -115,21 +115,3 @@ caption,
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Urbanist";
|
|
||||||
src:
|
|
||||||
url("@/assets/fonts/Urbanist-MediumItalic.woff2") format("woff2"),
|
|
||||||
url("@/assets/fonts/Urbanist-MediumItalic.woff") format("woff");
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Urbanist";
|
|
||||||
src:
|
|
||||||
url("@/assets/fonts/Urbanist-BoldItalic.woff2") format("woff2"),
|
|
||||||
url("@/assets/fonts/Urbanist-BoldItalic.woff") format("woff");
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
@ -14,17 +14,17 @@ html {
|
||||||
border-radius: 60px;
|
border-radius: 60px;
|
||||||
|
|
||||||
.button-content {
|
.button-content {
|
||||||
border: none;
|
border: 1px solid $red;
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
@include box-shadow-hover($red-200);
|
|
||||||
z-index: 5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="radio"]:focus+.list-button-content {
|
input[type="radio"]:focus+.list-button-content {
|
||||||
box-shadow: 0 0 0 2.5px $red;
|
box-shadow: 0 0 0 2.5px $red;
|
||||||
}
|
}
|
||||||
|
&:not(.selected):not(.no-hover):hover {
|
||||||
|
position: relative;
|
||||||
|
@include box-shadow-hover($red-200);
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.list-card {
|
&.list-card {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
.base-input-button {
|
.base-input-button {
|
||||||
&:not(.has-error):hover {
|
&:not(.disabled) {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.has-error):hover {
|
||||||
&.list-button,
|
&.list-button,
|
||||||
&.list-button-horizontal,
|
&.list-button-horizontal,
|
||||||
&.list-card {
|
&.list-card {
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,13 @@ $orange: #fd7e14;
|
||||||
$teal: #20c997;
|
$teal: #20c997;
|
||||||
$cyan: #0dcaf0;
|
$cyan: #0dcaf0;
|
||||||
|
|
||||||
|
//Heritage
|
||||||
|
$heritage-blue-primary: #0070D1;
|
||||||
|
$heritage-blue-secondary: #167cac;
|
||||||
|
|
||||||
// Darker gray
|
// Darker gray
|
||||||
$darker-gray: #525656;
|
$darker-gray: #525656;
|
||||||
|
$lighter-gray: #9aa1a3;
|
||||||
|
|
||||||
// scss-docs-start colors-map
|
// scss-docs-start colors-map
|
||||||
$colors: (
|
$colors: (
|
||||||
|
|
@ -153,6 +158,7 @@ $font-weight-bolder: bolder;
|
||||||
$border-radius: 0.25rem;
|
$border-radius: 0.25rem;
|
||||||
$border-radius-sm: 0.2rem;
|
$border-radius-sm: 0.2rem;
|
||||||
$border-radius-lg: 0.5rem; //Used for buttons. Can be used for other things, of course.
|
$border-radius-lg: 0.5rem; //Used for buttons. Can be used for other things, of course.
|
||||||
|
$border-radius-xl: 1.375rem;
|
||||||
$border-radius-pill: 50rem;
|
$border-radius-pill: 50rem;
|
||||||
$border-radius-list-button: 1.375rem; // Used for list buttons
|
$border-radius-list-button: 1.375rem; // Used for list buttons
|
||||||
|
|
||||||
|
|
@ -205,6 +211,11 @@ $alert-bg-scale: -90%;
|
||||||
$alert-border-scale: -100%;
|
$alert-border-scale: -100%;
|
||||||
$alert-color-scale: 40%;
|
$alert-color-scale: 40%;
|
||||||
|
|
||||||
|
$alert-yellow-bg: #fef6e8;
|
||||||
|
$alert-yellow-color: #e86421;
|
||||||
|
$alert-red-bg: #fbe9e8;
|
||||||
|
$alert-red-color: $red;
|
||||||
|
|
||||||
//Modal animation
|
//Modal animation
|
||||||
// This affects all [Bootstrap] modals
|
// This affects all [Bootstrap] modals
|
||||||
$modal-fade-transform: translate(0, 100%);
|
$modal-fade-transform: translate(0, 100%);
|
||||||
|
|
@ -216,9 +227,5 @@ $enable-important-utilities: false;
|
||||||
// Letter Spacing
|
// Letter Spacing
|
||||||
$letter-spacing-primary: 0.03em;
|
$letter-spacing-primary: 0.03em;
|
||||||
// Borders
|
// Borders
|
||||||
$border-input: 1px solid rgba(179, 180, 181);
|
$border-input: 1px solid $gray-350;
|
||||||
$border-input-focus: 2.5px solid $blue;
|
$border-input-focus: 2.5px solid $heritage-blue-primary;
|
||||||
|
|
||||||
//Heritage
|
|
||||||
$heritage-blue-primary: #0070D1;
|
|
||||||
$heritage-blue-secondary: #167cac;
|
|
||||||
|
|
|
||||||
|
|
@ -1,64 +1,85 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="alert fade show text-center mb-0 py-2 px-4"
|
class="alert fade show"
|
||||||
role="alert"
|
role="alert"
|
||||||
:class="[
|
:class="[
|
||||||
isDismissible ? 'alert-dismissible' : '',
|
isDismissible ? 'alert-dismissible' : '',
|
||||||
alertClass,
|
alertClass,
|
||||||
cssClassNameForCmsWidget,
|
cssClassNameForCmsWidget,
|
||||||
]">
|
]">
|
||||||
<p class="m-0 fw-bold alert-heading">
|
<div class="alert-header">
|
||||||
{{ alertHeadline }}
|
<svg class="alert-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="21" viewBox="0 0 20 21">
|
||||||
</p>
|
<path fill-rule="nonzero" d="M10 .5c5.523 0 10 4.477 10 10s-4.477 10-10 10-10-4.477-10-10S4.477.5 10 .5zm.043 13.6a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm.77-9.2h-1.54l-.088.009a.502.502 0 0 0-.299.193.66.66 0 0 0-.125.47l.747 6.806.017.096c.065.249.263.425.495.426l.084-.008c.22-.042.396-.246.427-.51l.793-6.805.004-.102a.651.651 0 0 0-.127-.37.489.489 0 0 0-.388-.205z"/>
|
||||||
<template
|
|
||||||
v-for="paragraph in splitAlertCopyForParagraphTag"
|
|
||||||
:key="paragraph">
|
|
||||||
<p
|
|
||||||
v-if="!doesCopyContainRouterLink(paragraph) && !doesCopyContainTextLink(paragraph)"
|
|
||||||
class="m-0 text-body small"
|
|
||||||
v-html="paragraph"></p>
|
|
||||||
<p
|
|
||||||
v-else
|
|
||||||
class="m-0 text-body small">
|
|
||||||
<template v-if="doesCopyContainRouterLink(paragraph)">
|
|
||||||
<textBlock
|
|
||||||
:customText="paragraph"
|
|
||||||
class="mb-1"
|
|
||||||
marginTopSizeOverride="0" />
|
|
||||||
</template>
|
|
||||||
<template
|
|
||||||
v-for="copy in splitCopyOnCMSPlaceHolder(paragraph)"
|
|
||||||
v-else
|
|
||||||
:key="copy">
|
|
||||||
<span v-if="doesCopyContainTextLink(copy)">
|
|
||||||
<textLink
|
|
||||||
linkType="text"
|
|
||||||
:text="getRouterLinkDisplayTextFromCopy(copy)"
|
|
||||||
href="#!"
|
|
||||||
:data-bs-target="'#' + getRouterLinkRouteFromCopy(copy)"
|
|
||||||
@click-event="
|
|
||||||
$emit('textLinkClicked', getRouterLinkRouteFromCopy(copy))
|
|
||||||
" />
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
v-html="copy"></span>
|
|
||||||
</template>
|
|
||||||
</p>
|
|
||||||
</template>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn-close p-2"
|
|
||||||
data-bs-dismiss="alert"
|
|
||||||
aria-label="Close">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 23.7 23.7"
|
|
||||||
xml:space="preserve">
|
|
||||||
<path
|
|
||||||
d="m23.24 2.7-9.15 9.15L23.24 21a1.581 1.581 0 0 1-1.12 2.7c-.42 0-.82-.16-1.12-.46l-9.15-9.15-9.15 9.15c-.3.3-.7.46-1.12.46A1.581 1.581 0 0 1 .46 21l8.47-8.47.68-.68L.46 2.7c-.62-.62-.62-1.62 0-2.24.62-.62 1.62-.62 2.24 0l8.47 8.47.68.68L21 .46a1.57 1.57 0 0 1 2.23 0c.63.62.63 1.62.01 2.24z" />
|
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
<span class="m-0 fw-bold alert-header-text">
|
||||||
|
{{ alertHeadline }}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
v-if="isCollapsible"
|
||||||
|
class="btn-collapse"
|
||||||
|
type="button"
|
||||||
|
@click="toggleCollapse">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="9" viewBox="0 0 15 9"
|
||||||
|
class="btn-collapse-icon"
|
||||||
|
:class="{ 'rotated': !collapsed }">
|
||||||
|
<path fill-rule="nonzero" d="M7.5 0a.806.806 0 0 0-.593.265L.246 7.455a.957.957 0 0 0 0 1.28.796.796 0 0 0 1.185 0l6.07-6.55 6.068 6.55a.796.796 0 0 0 1.186 0 .957.957 0 0 0 0-1.28L8.093.265A.806.806 0 0 0 7.5 0"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="isDismissible"
|
||||||
|
type="button"
|
||||||
|
class="btn-close p-2"
|
||||||
|
data-bs-dismiss="alert"
|
||||||
|
aria-label="Close">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 23.7 23.7"
|
||||||
|
xml:space="preserve">
|
||||||
|
<path
|
||||||
|
d="m23.24 2.7-9.15 9.15L23.24 21a1.581 1.581 0 0 1-1.12 2.7c-.42 0-.82-.16-1.12-.46l-9.15-9.15-9.15 9.15c-.3.3-.7.46-1.12.46A1.581 1.581 0 0 1 .46 21l8.47-8.47.68-.68L.46 2.7c-.62-.62-.62-1.62 0-2.24.62-.62 1.62-.62 2.24 0l8.47 8.47.68.68L21 .46a1.57 1.57 0 0 1 2.23 0c.63.62.63 1.62.01 2.24z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="alert-body"
|
||||||
|
v-show="alertCopy"
|
||||||
|
:id="collapseId">
|
||||||
|
<template
|
||||||
|
v-for="paragraph in splitAlertCopyForParagraphTag"
|
||||||
|
:key="paragraph">
|
||||||
|
<p
|
||||||
|
v-if="!doesCopyContainRouterLink(paragraph) && !doesCopyContainTextLink(paragraph)"
|
||||||
|
class="m-0 text-body"
|
||||||
|
v-html="paragraph"></p>
|
||||||
|
<p
|
||||||
|
v-else
|
||||||
|
class="m-0 text-body">
|
||||||
|
<template v-if="doesCopyContainRouterLink(paragraph)">
|
||||||
|
<textBlock
|
||||||
|
:customText="paragraph"
|
||||||
|
class="mb-1"
|
||||||
|
marginTopSizeOverride="0" />
|
||||||
|
</template>
|
||||||
|
<template
|
||||||
|
v-for="copy in splitCopyOnCMSPlaceHolder(paragraph)"
|
||||||
|
v-else
|
||||||
|
:key="copy">
|
||||||
|
<span v-if="doesCopyContainTextLink(copy)">
|
||||||
|
<textLink
|
||||||
|
linkType="text"
|
||||||
|
:text="getRouterLinkDisplayTextFromCopy(copy)"
|
||||||
|
href="#!"
|
||||||
|
:data-bs-target="'#' + getRouterLinkRouteFromCopy(copy)"
|
||||||
|
@click-event="
|
||||||
|
$emit('textLinkClicked', getRouterLinkRouteFromCopy(copy))
|
||||||
|
" />
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
v-html="copy"></span>
|
||||||
|
</template>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -74,6 +95,7 @@ import {
|
||||||
import applicationConfig from '@/constants/application-config';
|
import applicationConfig from '@/constants/application-config';
|
||||||
import textBlock from '@/digital-components/text-block/text-block.vue';
|
import textBlock from '@/digital-components/text-block/text-block.vue';
|
||||||
import textLink from '@/ux-components/text-link/text-link.vue';
|
import textLink from '@/ux-components/text-link/text-link.vue';
|
||||||
|
import { Collapse } from 'bootstrap';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// eslint-disable-next-line vue/multi-word-component-names
|
// eslint-disable-next-line vue/multi-word-component-names
|
||||||
|
|
@ -84,13 +106,14 @@ export default {
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
isDismissible: Boolean,
|
isDismissible: Boolean,
|
||||||
|
isCollapsible: Boolean,
|
||||||
/*
|
/*
|
||||||
alertClass class names:
|
alertClass class names:
|
||||||
alert-success (green)
|
alert-success (green)
|
||||||
alert-danger (red)
|
alert-danger (red)
|
||||||
alert-warning (yellow)
|
alert-warning (yellow)
|
||||||
alert-info (blue)
|
alert-info (blue)
|
||||||
*/
|
*/
|
||||||
alertClass: String,
|
alertClass: String,
|
||||||
cmsWidgetName: {
|
cmsWidgetName: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
@ -106,7 +129,14 @@ export default {
|
||||||
shouldScrollToOnMount: {
|
shouldScrollToOnMount: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
}
|
},
|
||||||
|
startCollapsed: Boolean
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
collapsed: this.startCollapsed || false,
|
||||||
|
collapseElement: null,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
pageQueryString() {
|
pageQueryString() {
|
||||||
|
|
@ -124,10 +154,18 @@ export default {
|
||||||
},
|
},
|
||||||
splitAlertCopyForParagraphTag() {
|
splitAlertCopyForParagraphTag() {
|
||||||
return splitCMSCopyOnParagraphTag(this.alertCopy);
|
return splitCMSCopyOnParagraphTag(this.alertCopy);
|
||||||
}
|
},
|
||||||
|
collapseId() {
|
||||||
|
return this.isCollapsible
|
||||||
|
? `${this.cmsWidgetName}-collapse`
|
||||||
|
: null;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.ensureAlertIsInViewPort();
|
this.ensureAlertIsInViewPort();
|
||||||
|
if (this.isCollapsible) {
|
||||||
|
this.collapseElement = Collapse.getOrCreateInstance(document.getElementById(this.collapseId));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
doesCopyContainRouterLink,
|
doesCopyContainRouterLink,
|
||||||
|
|
@ -152,37 +190,82 @@ export default {
|
||||||
<= (window.innerHeight - footerHeight
|
<= (window.innerHeight - footerHeight
|
||||||
|| document.documentElement.clientHeight - footerHeight)
|
|| document.documentElement.clientHeight - footerHeight)
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
toggleCollapse() {
|
||||||
|
this.collapsed = !this.collapsed;
|
||||||
|
if (this.collapsed) {
|
||||||
|
this.collapseElement.hide();
|
||||||
|
} else {
|
||||||
|
this.collapseElement.show();
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.alert {
|
.alert {
|
||||||
|
border: 1px solid;
|
||||||
|
padding: 0rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
.alert-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: .75rem 1rem .75rem 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $black;
|
||||||
|
}
|
||||||
|
.alert-icon {
|
||||||
|
min-width: 1rem;
|
||||||
|
max-height: 1rem;
|
||||||
|
}
|
||||||
|
.alert-header-text {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 0rem .625rem;
|
||||||
|
}
|
||||||
|
.btn-collapse {
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
min-width: 1rem;
|
||||||
|
max-height: 1rem;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.btn-collapse-icon {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
&.rotated {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
button {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.btn-close {
|
.btn-close {
|
||||||
background: none;
|
background: none;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
width: 0.75rem;
|
width: 0.75rem;
|
||||||
height: 0.75rem;
|
height: 0.75rem;
|
||||||
|
top: 2px;
|
||||||
|
right: 2px;
|
||||||
}
|
}
|
||||||
&.alert-dismissible {
|
.alert-body {
|
||||||
button {
|
margin: 0 1rem;
|
||||||
display: flex;
|
padding: .5rem 1rem .75rem 1rem;
|
||||||
top: 2px;
|
border-top: 1px solid;
|
||||||
right: 2px;
|
color: $darker-gray;
|
||||||
}
|
line-height: 26px;
|
||||||
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
&.alert-info {
|
&.alert-info {
|
||||||
background-color: $blue-100;
|
background-color: $blue-100;
|
||||||
.alert-heading {
|
border-color: $blue-200;
|
||||||
color: $blue-700;
|
.alert-body {
|
||||||
|
border-top-color: $blue-200;
|
||||||
}
|
}
|
||||||
svg {
|
svg {
|
||||||
fill: $blue-700;
|
fill: $blue-700;
|
||||||
|
|
@ -191,9 +274,10 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.alert-danger {
|
&.alert-danger {
|
||||||
background-color: $red-100;
|
background-color: $alert-red-bg;
|
||||||
.alert-heading {
|
border-color: $alert-red-color;
|
||||||
color: $red-600;
|
.alert-body {
|
||||||
|
border-top-color: $alert-red-color;
|
||||||
}
|
}
|
||||||
svg {
|
svg {
|
||||||
fill: $red-600;
|
fill: $red-600;
|
||||||
|
|
@ -202,20 +286,20 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.alert-warning {
|
&.alert-warning {
|
||||||
background-color: $yellow-100;
|
background-color: $alert-yellow-bg;
|
||||||
.alert-heading {
|
border-color: $alert-yellow-color;
|
||||||
color: $yellow-600;
|
.alert-body {
|
||||||
|
border-top-color: $alert-yellow-color;
|
||||||
}
|
}
|
||||||
svg {
|
svg {
|
||||||
fill: $yellow-600;
|
fill: $alert-yellow-color;
|
||||||
width: 1rem;
|
|
||||||
height: 1rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.alert-success {
|
&.alert-success {
|
||||||
background-color: $green-100;
|
background-color: $green-100;
|
||||||
.alert-heading {
|
border-color: $green-200;
|
||||||
color: $green-700;
|
.alert-body {
|
||||||
|
border-top-color: $green-200;
|
||||||
}
|
}
|
||||||
svg {
|
svg {
|
||||||
fill: $green-700;
|
fill: $green-700;
|
||||||
|
|
@ -223,9 +307,5 @@ export default {
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
& p {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
margin-bottom: 0.25rem !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import { getMountOptions } from '@/helpers/unit-test-helper.js';
|
import { getMountOptions } from '@/helpers/unit-test-helper.js';
|
||||||
import buttonMain from '@/ux-components/button-main/button-main.vue';
|
import buttonMain from '@/ux-components/button-main/button-main.vue';
|
||||||
import buttonVariants from '@/constants/button-variants';
|
import { buttonVariants } from '@/constants/component-variants';
|
||||||
import buttonSizes from '@/constants/button-sizes';
|
import buttonSizes from '@/constants/button-sizes';
|
||||||
|
|
||||||
/** @ignore */
|
/** @ignore */
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import buttonVariants from '@/constants/button-variants';
|
import { buttonVariants } from '@/constants/component-variants';
|
||||||
import buttonSizes from '@/constants/button-sizes';
|
import buttonSizes from '@/constants/button-sizes';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,10 @@
|
||||||
buttonWrapperClasses="list-group base-input-button list-button no-hover d-flex flex-column w-100 mb-2">
|
buttonWrapperClasses="list-group base-input-button list-button no-hover d-flex flex-column w-100 mb-2">
|
||||||
<div
|
<div
|
||||||
:aria-label="buttonLabel"
|
:aria-label="buttonLabel"
|
||||||
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4">
|
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4"
|
||||||
|
:class="{
|
||||||
|
'has-sub-copy': buttonLabelSubCopy,
|
||||||
|
}">
|
||||||
<span
|
<span
|
||||||
class="m-0"
|
class="m-0"
|
||||||
:class="textPosition">
|
:class="textPosition">
|
||||||
|
|
@ -85,6 +88,10 @@ $heritage-checked-border-color: #0070d1;
|
||||||
background: $heritage-checked-background-color;
|
background: $heritage-checked-background-color;
|
||||||
border-color: $heritage-checked-border-color;
|
border-color: $heritage-checked-border-color;
|
||||||
box-shadow: 0 0 0 1px $blue;
|
box-shadow: 0 0 0 1px $blue;
|
||||||
|
&:not(.has-sub-copy) {
|
||||||
|
font-weight: 500;
|
||||||
|
color: $black;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&:checked + .list-button-content span:nth-child(2) {
|
&:checked + .list-button-content span:nth-child(2) {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
@ -104,10 +111,15 @@ $heritage-checked-border-color: #0070d1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
|
&:not(.has-sub-copy) {
|
||||||
|
font-weight: 400;
|
||||||
|
color: $darker-gray;
|
||||||
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
&.small {
|
&.small {
|
||||||
color: $darker-gray;
|
color: $darker-gray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -62,7 +62,7 @@ export default {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
a {
|
a {
|
||||||
color: #0070d1;
|
color: $heritage-blue-primary;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
line-height: 1.56;
|
line-height: 1.56;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue