Merge branch 'release/2025.05.08' into feature/CASH-188
This commit is contained in:
commit
dd5c2a8264
32 changed files with 747 additions and 1573 deletions
6
src/assets/img/icons/calendar-icon-black.svg
Normal file
6
src/assets/img/icons/calendar-icon-black.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.65567 6.44287H13.35" stroke="#0A0A0A" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.4256 2V3.97449" stroke="#0A0A0A" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.57864 2V3.97449" stroke="#0A0A0A" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.5427 2.94792H5.4625C3.70011 2.94792 2.60001 3.92933 2.60001 5.73323V11.1637C2.60001 12.9962 3.70011 14.0003 5.4625 14.0003H10.5375C12.3051 14.0003 13.4001 13.0137 13.4001 11.2091V5.73323C13.4052 3.92933 12.3103 2.94792 10.5427 2.94792Z" stroke="#0A0A0A" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 748 B |
6
src/assets/img/icons/calendar-icon-grey.svg
Normal file
6
src/assets/img/icons/calendar-icon-grey.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.15567 6.44287H13.85" stroke="#4B4C4E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.9256 2V3.97449" stroke="#4B4C4E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.07864 2V3.97449" stroke="#4B4C4E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.0427 2.94791H5.9625C4.20011 2.94791 3.10001 3.92931 3.10001 5.73321V11.1637C3.10001 12.9962 4.20011 14.0003 5.9625 14.0003H11.0375C12.8051 14.0003 13.9001 13.0137 13.9001 11.2091V5.73321C13.9052 3.92931 12.8103 2.94791 11.0427 2.94791Z" stroke="#4B4C4E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 748 B |
4
src/assets/img/icons/info-circle.svg
Normal file
4
src/assets/img/icons/info-circle.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 12C3 16.9699 7.02908 21 12 21C16.9709 21 21 16.9699 21 12C21 7.02908 16.9709 3 12 3C7.02908 3 3 7.02908 3 12Z" stroke="#0070D1" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.0057 15.6932V11.3936M12 8.35426V8.29102" stroke="#0070D1" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 450 B |
6
src/assets/img/icons/payment-icon-green.svg
Normal file
6
src/assets/img/icons/payment-icon-green.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 13.8143H13.5894" stroke="#00723E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.20013 2.88802L3.08796 7.00022C2.09364 7.99456 2.17407 9.32289 3.17099 10.3198L5.86013 13.0096C6.85706 14.0065 8.18213 14.0896 9.18039 13.092L13.2919 8.98042C14.2901 7.98222 14.2065 6.6571 13.2095 5.66018L10.5197 2.97039C9.52286 1.97348 8.19773 1.89046 7.20013 2.88802Z" stroke="#00723E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3.6315 10.782L10.9809 3.4326" stroke="#00723E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.51559 11.2542L8.72786 10.042" stroke="#00723E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 800 B |
6
src/assets/img/icons/payment-icon-grey.svg
Normal file
6
src/assets/img/icons/payment-icon-grey.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.5 13.8143H14.0894" stroke="#4B4C4E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.70014 2.88802L3.58797 7.00022C2.59365 7.99456 2.67408 9.32289 3.671 10.3198L6.36014 13.0096C7.35707 14.0065 8.68214 14.0896 9.68041 13.092L13.7919 8.98042C14.7901 7.98222 14.7065 6.6571 13.7095 5.66018L11.0197 2.97039C10.0229 1.97348 8.69774 1.89046 7.70014 2.88802Z" stroke="#4B4C4E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4.13152 10.782L11.4809 3.43262" stroke="#4B4C4E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.01558 11.2543L9.22785 10.042" stroke="#4B4C4E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 802 B |
26
src/constants/progress-bar-mapper.js
Normal file
26
src/constants/progress-bar-mapper.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
const pagePercentageMapper = {
|
||||
vehicle: 4,
|
||||
"vehicle-damage": 16,
|
||||
estimate: 28,
|
||||
"service-zip": 32,
|
||||
"vin-lookup": 32,
|
||||
"license-plate-lookup": 32,
|
||||
"address-lookup": 32,
|
||||
"address-vehicles": 36,
|
||||
"part-questions": 40,
|
||||
"molding-questions": 40,
|
||||
"vehicle-parts": 40,
|
||||
"capability-questions": 40,
|
||||
quote: 48,
|
||||
"insurance-company": 52,
|
||||
"service-location": 60,
|
||||
schedule: 72,
|
||||
"customer-details": 84,
|
||||
"payment-method": 92,
|
||||
payment: 96,
|
||||
confirmation: 100,
|
||||
};
|
||||
|
||||
export const getProgressBarPercentage = (page) => {
|
||||
return pagePercentageMapper[page] || 0;
|
||||
};
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
<template>
|
||||
<div class="date-picker text-center" :class="calendarViewDirection">
|
||||
<div
|
||||
class="date-picker text-center"
|
||||
:class="`${calendarViewDirection} ${isPricingByDayClass} ${showPricingByDayClass}`">
|
||||
<fieldset id="date-picker-fieldset" ref="datePickerFieldset">
|
||||
<legend class="sr-only">Select a day and time</legend>
|
||||
<div
|
||||
|
|
@ -60,18 +62,24 @@
|
|||
date.dateNum === 1 ? 'first-day-' + month.startDateDayIndex : '',
|
||||
date.dayClasses,
|
||||
date.isSelectable ? 'selectable-day' : '',
|
||||
date.isPricingByDayUpchargeDay ? 'upch-day' : '',
|
||||
]">
|
||||
<input
|
||||
:disabled="!date.isSelectable"
|
||||
type="radio"
|
||||
name="day-of-month"
|
||||
v-model="selectedDate"
|
||||
@click="fireDateSelectedEvent"
|
||||
@keypress.enter="fireDateSelectedEvent"
|
||||
@click="fireDateSelectedEvent($event, date)"
|
||||
@keypress.enter="fireDateSelectedEvent($event, date)"
|
||||
:value="date.inputValue"
|
||||
:id="`${month.monthLabel}-${date.dateNum.toString()}`" />
|
||||
<label :for="`${month.monthLabel}-${date.dateNum.toString()}`">
|
||||
<span>{{ date.dateNum.toString() }}</span>
|
||||
<span
|
||||
v-if="isPricingByDayExperiment && showPricingByDay && date.isSelectable"
|
||||
class="price">
|
||||
{{ date.priceString }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -99,8 +107,17 @@
|
|||
// Supporting files
|
||||
import loader from "@/ux-components/loader/loader";
|
||||
import store from "@/store";
|
||||
import { TIMINGFUNC_MAP, BUFFER_OFFSET, MONTHS_OF_YEAR } from "./mixins/constants";
|
||||
import { selectableDaysOptions, requiredParameter, forceTwoDigitString } from "./mixins/helpers";
|
||||
import {
|
||||
TIMINGFUNC_MAP,
|
||||
BUFFER_OFFSET,
|
||||
MONTHS_OF_YEAR,
|
||||
DAYS_OF_WEEK,
|
||||
} from "@/digital-components/date-picker/mixins/constants";
|
||||
import {
|
||||
selectableDaysOptions,
|
||||
requiredParameter,
|
||||
forceTwoDigitString,
|
||||
} from "@/digital-components/date-picker/mixins/helpers";
|
||||
import {
|
||||
convertDateToDateString,
|
||||
convertDateStringToDate,
|
||||
|
|
@ -148,6 +165,10 @@ export default {
|
|||
type: String,
|
||||
default: "",
|
||||
},
|
||||
showPricingByDay: Boolean,
|
||||
pricingByDayBasePrice: Number,
|
||||
pricingByDayUpcharge: Number,
|
||||
isPricingByDayExperiment: Boolean,
|
||||
},
|
||||
setup(props) {
|
||||
const uuid = uuidv4();
|
||||
|
|
@ -199,6 +220,12 @@ export default {
|
|||
if (this.selectableDatesSetting === "custom") return "future";
|
||||
return "past";
|
||||
},
|
||||
isPricingByDayClass() {
|
||||
return this.isPricingByDayExperiment ? "pricing-by-day" : "";
|
||||
},
|
||||
showPricingByDayClass() {
|
||||
return this.showPricingByDay ? "show-pricing-by-day" : "";
|
||||
},
|
||||
selectedDate: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
|
|
@ -212,12 +239,12 @@ export default {
|
|||
initializeComponent(initialData) {
|
||||
this.setCalendarData(initialData);
|
||||
},
|
||||
fireDateSelectedEvent(event) {
|
||||
fireDateSelectedEvent(event, date) {
|
||||
// Ignore if arrow key selected radioButton
|
||||
if (event.screenX === 0 && event.screenY === 0) {
|
||||
return;
|
||||
}
|
||||
this.$emit("date-clicked");
|
||||
this.$emit("date-clicked", date);
|
||||
},
|
||||
getWeekStartDate(dateString) {
|
||||
const date = convertDateStringToDate(dateString);
|
||||
|
|
@ -440,6 +467,8 @@ export default {
|
|||
initialViewEndDate: config.initialViewEndDate,
|
||||
hideSecondMonth: hideSecondMonth,
|
||||
preSelectedDate: config.preSelectedDate,
|
||||
pricingByDayBasePrice: config.pricingByDayBasePrice,
|
||||
pricingByDayUpcharge: config.pricingByDayUpcharge,
|
||||
};
|
||||
if (direction === "future") {
|
||||
// first 0, then 1
|
||||
|
|
@ -573,6 +602,17 @@ export default {
|
|||
("0" + monthNum).slice(-2) +
|
||||
"-" +
|
||||
("0" + i).slice(-2);
|
||||
const dayIndex = convertDateStringToDate(dateString).getDay();
|
||||
const dayObject = DAYS_OF_WEEK[dayIndex];
|
||||
const isSelectable =
|
||||
this.selectableDatesData.findIndex((date) => date.date === dateString) > -1
|
||||
? true
|
||||
: false;
|
||||
const isPricingByDayUpchargeDay = dayObject.isPricingByDayUpchargeDay;
|
||||
const displayPrice = isPricingByDayUpchargeDay
|
||||
? options.pricingByDayBasePrice + options.pricingByDayUpcharge
|
||||
: options.pricingByDayBasePrice;
|
||||
const priceString = "$" + displayPrice;
|
||||
|
||||
if (offset === 0 && i === this.todayDateNum) {
|
||||
dayClasses += " current-day";
|
||||
|
|
@ -583,8 +623,8 @@ export default {
|
|||
if (offset === 0 && i > this.todayDateNum && calendarViewDirection === "past") {
|
||||
dayClasses += " unavailable-day";
|
||||
}
|
||||
if (convertDateStringToDate(dateString).getDay() === 0) {
|
||||
dayClasses += " sunday";
|
||||
if (dayIndex === 0) {
|
||||
dayClasses += " " + dayObject.cssClass;
|
||||
}
|
||||
if (
|
||||
this.hideSomeDaysForInitialView &&
|
||||
|
|
@ -598,10 +638,9 @@ export default {
|
|||
dateNum: i,
|
||||
dayClasses: dayClasses,
|
||||
inputValue: dateString,
|
||||
isSelectable:
|
||||
this.selectableDatesData.findIndex((date) => date.date === dateString) > -1
|
||||
? true
|
||||
: false,
|
||||
priceString: priceString,
|
||||
isSelectable: isSelectable,
|
||||
isPricingByDayUpchargeDay: isPricingByDayUpchargeDay,
|
||||
};
|
||||
dates.push(dateObject);
|
||||
}
|
||||
|
|
@ -897,7 +936,6 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:checked {
|
||||
@include media-breakpoint-up(sm) {
|
||||
box-shadow: 0 0 0 4px transparent;
|
||||
|
|
@ -924,9 +962,7 @@ export default {
|
|||
|
||||
&.selectable-day {
|
||||
label {
|
||||
color: $blue;
|
||||
background-color: $blue-100;
|
||||
border: 1px solid $blue;
|
||||
min-width: 2.5rem;
|
||||
width: 2.5rem;
|
||||
border-radius: 50%;
|
||||
|
|
@ -934,12 +970,11 @@ export default {
|
|||
}
|
||||
&.unavailable-day {
|
||||
label {
|
||||
color: $gray-500;
|
||||
background-color: $gray-100;
|
||||
border: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
//NEEDED?
|
||||
&.unavailable-day:not(&.sunday) {
|
||||
label {
|
||||
&::before {
|
||||
|
|
@ -949,7 +984,6 @@ export default {
|
|||
left: -1rem;
|
||||
width: 1rem;
|
||||
height: 2.5rem;
|
||||
background: $gray-100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1056,7 +1090,107 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
// pricing by day override styles
|
||||
&.pricing-by-day {
|
||||
.calendar-grid-container .grid-item {
|
||||
margin: 0 0 0.15rem 0;
|
||||
}
|
||||
.month-year {
|
||||
grid-area: 1 / 1 / 2 / 8;
|
||||
}
|
||||
.legend {
|
||||
display: none;
|
||||
}
|
||||
.radio-wrapper {
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
height: 2.5rem;
|
||||
color: $black;
|
||||
|
||||
input[type="radio"] {
|
||||
&:focus-visible + label {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:focus + label,
|
||||
&:checked:focus + label {
|
||||
box-shadow: none;
|
||||
background-color: $white;
|
||||
color: $black;
|
||||
}
|
||||
&:checked + label {
|
||||
background: $blue-100;
|
||||
border: 2px solid $blue;
|
||||
border-radius: 0.25rem;
|
||||
color: $black;
|
||||
span {
|
||||
font-family: AvertaSemibold;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
label {
|
||||
flex-direction: column;
|
||||
height: 2.5rem;
|
||||
width: 100%;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.2;
|
||||
justify-content: center;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
&.selectable-day {
|
||||
label {
|
||||
color: $black;
|
||||
background-color: $blue-100;
|
||||
min-width: 2.5rem;
|
||||
width: 2.5rem;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
}
|
||||
&.current-day {
|
||||
label:after {
|
||||
margin-top: 0.2rem;
|
||||
position: relative;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.show-pricing-by-day {
|
||||
.radio-wrapper {
|
||||
&.selectable-day label span {
|
||||
text-decoration: none;
|
||||
color: $black;
|
||||
font-weight: 400;
|
||||
font-family: "AvertaSemibold";
|
||||
|
||||
&.price {
|
||||
font-family: "AvertaRegular";
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
&.upch-day label span.price {
|
||||
color: $gray-600;
|
||||
font-weight: 400;
|
||||
font-family: $font-family-sans-serif;
|
||||
}
|
||||
input[type="radio"] {
|
||||
&:focus + label,
|
||||
&:checked:focus + label {
|
||||
color: $gray-600;
|
||||
}
|
||||
&:checked + label {
|
||||
color: $blue;
|
||||
span {
|
||||
font-weight: 400;
|
||||
font-family: "AvertaSemibold";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
display: block;
|
||||
position: relative;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -5,6 +5,7 @@
|
|||
:class="[
|
||||
this.buttonLabelSubCopy ? 'has-subheader' : '',
|
||||
this.additionalButtonData.isInsuranceSelected ? 'is-insurance' : '',
|
||||
this.hasPackageDiscount() ? 'has-package-discount' : '',
|
||||
]"
|
||||
for="testradio">
|
||||
<div class="package-specs">
|
||||
|
|
@ -54,12 +55,7 @@
|
|||
v-html="this.buttonFooterCopy"></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="
|
||||
this.additionalButtonData.servicePackageDiscount &&
|
||||
this.additionalButtonData.Text
|
||||
"
|
||||
class="special-save-box">
|
||||
<div v-if="hasPackageDiscount()" class="special-save-box">
|
||||
<span v-html="this.additionalButtonData.Text"></span>
|
||||
</div>
|
||||
<span class="pricing-info">
|
||||
|
|
@ -78,7 +74,7 @@
|
|||
<span>in 4 interest-free payments</span>
|
||||
<div>
|
||||
or
|
||||
<span v-html="this.buttonAuxillaryCopy"></span>
|
||||
<span v-html="truncatedSinglePayment()"></span>
|
||||
<span
|
||||
class="strikethrough-price"
|
||||
v-if="shouldDisplayStrikeThroughPrice"
|
||||
|
|
@ -141,6 +137,14 @@ export default {
|
|||
const decimalPrice = parseFloat(this.buttonAuxillaryCopy.replace("$", ""));
|
||||
return "$" + (decimalPrice / 4).toFixed(2);
|
||||
},
|
||||
truncatedSinglePayment() {
|
||||
return "$" + Math.trunc(this.buttonAuxillaryCopy.replace("$", ""));
|
||||
},
|
||||
hasPackageDiscount() {
|
||||
return (
|
||||
this.additionalButtonData.servicePackageDiscount && this.additionalButtonData.Text
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -209,12 +213,6 @@ export default {
|
|||
}
|
||||
|
||||
&.has-subheader {
|
||||
min-height: 150px;
|
||||
|
||||
&.is-insurance {
|
||||
min-height: 72px;
|
||||
}
|
||||
|
||||
& > .package-specs {
|
||||
& > div:first-of-type {
|
||||
display: flex;
|
||||
|
|
@ -222,6 +220,10 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
&.has-package-discount {
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
position: relative;
|
||||
|
|
|
|||
|
|
@ -32,17 +32,13 @@
|
|||
linkType="navigation"
|
||||
text="Cookie preferences"
|
||||
href="javascript:OneTrust.ToggleInfoDisplay();" />
|
||||
<textLink
|
||||
linkType="newWindowLink"
|
||||
text="Warranty"
|
||||
href="//www.safelite.com/national-lifetime-warranty"
|
||||
target="_blank" />
|
||||
<textLink
|
||||
linkType="newWindowLink"
|
||||
text="Notice at collection"
|
||||
href="https://www.safelite.com/ccpa-privacy-policy"
|
||||
target="_blank" />
|
||||
</div>
|
||||
<p class="mx-3 my-0 pb-1">© 2025 Safelite Group</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -64,8 +60,9 @@ export default {
|
|||
flex-direction: column;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
align-items: end;
|
||||
align-items: center;
|
||||
margin-top: auto;
|
||||
padding: 0 0 2rem 0;
|
||||
|
||||
.skyline {
|
||||
height: 96px;
|
||||
|
|
@ -74,22 +71,34 @@ export default {
|
|||
height: 48px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.25rem;
|
||||
@include media-breakpoint-up(md) {
|
||||
font-size: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-inner-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-top: 1px solid $gray;
|
||||
padding: 2rem 0;
|
||||
padding: 2rem 0 0 0;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: $gray-600;
|
||||
|
||||
:deep(a) {
|
||||
text-decoration: none;
|
||||
&.new-window-link {
|
||||
margin: 0 0.75rem;
|
||||
color: $gray-600;
|
||||
}
|
||||
&.navigation-link {
|
||||
margin: 0 0.75rem;
|
||||
color: $gray-600;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<!-- // menu-modal temporarily not in use until the hamburger menu is re-implemented for use with the progress bar. See display: none below. -->
|
||||
<menuModal />
|
||||
<!-- // menu-modal temporarily not in use until the hamburger menu is re-implemented for use with the progress bar. See display: none below. -->
|
||||
<progressBar />
|
||||
</div>
|
||||
<template v-for="globalAlert in globalAlertMessages" :key="globalAlert.id">
|
||||
<transition name="fade" mode="out-in">
|
||||
|
|
@ -28,6 +29,7 @@ import eventBus from "@/helpers/event-bus/event-bus";
|
|||
import { globalEvents } from "@/constants/events";
|
||||
import menuModal from "@/fmg-components/funnel-header/menu-modal/menu-modal";
|
||||
import salesforceWebchat from "../../digital-components/salesforce-webchat/salesforce-webchat.vue";
|
||||
import progressBar from "@/fmg-components/funnel-header/progress-bar/progress-bar";
|
||||
|
||||
// Constants
|
||||
const ALERT_DURATION = 3000; // millisecond time to display alert before dismissal
|
||||
|
|
@ -69,6 +71,7 @@ export default {
|
|||
alert,
|
||||
menuModal,
|
||||
salesforceWebchat,
|
||||
progressBar,
|
||||
},
|
||||
mounted() {
|
||||
// Check if alert event is on the bus
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import progressBar from "./progress-bar";
|
||||
import store from "@/store";
|
||||
|
||||
describe("progressBar", () => {
|
||||
test("progress should be 0", () => {
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
const wrapper = shallowMount(progressBar, {
|
||||
mixins: [mockMixin],
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.progress).toBe(0);
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe("progressBar", () => {
|
||||
test("progress should be 4%", () => {
|
||||
// Arrange
|
||||
store.getters = {
|
||||
applicationUser: {
|
||||
lastPageVisited: "vehicle",
|
||||
},
|
||||
};
|
||||
|
||||
// Act
|
||||
const wrapper = shallowMount(progressBar, {
|
||||
mixins: [mockMixin],
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.progress).toBe(4);
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe("progressBar", () => {
|
||||
test("progress should be 48%", () => {
|
||||
// Arrange
|
||||
store.getters = {
|
||||
applicationUser: {
|
||||
lastPageVisited: "quote",
|
||||
},
|
||||
};
|
||||
|
||||
// Act
|
||||
const wrapper = shallowMount(progressBar, {
|
||||
mixins: [mockMixin],
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.progress).toBe(48);
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe("progressBar", () => {
|
||||
test("progress should be 100%", () => {
|
||||
// Arrange
|
||||
store.getters = {
|
||||
applicationUser: {
|
||||
lastPageVisited: "confirmation",
|
||||
},
|
||||
};
|
||||
|
||||
// Act
|
||||
const wrapper = shallowMount(progressBar, {
|
||||
mixins: [mockMixin],
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.progress).toBe(100);
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getProgress: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<template>
|
||||
<div id="progress-bar-container">
|
||||
<progress v-if="progress > 0" :value="progress" max="100" v-html="progress + '%'" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import store from "@/store";
|
||||
import { getProgressBarPercentage } from "@/constants/progress-bar-mapper";
|
||||
|
||||
export default {
|
||||
name: "progressBar",
|
||||
data() {
|
||||
return {
|
||||
progress: this.getProgress(),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getProgress() {
|
||||
var pageName = store.getters.applicationUser.lastPageVisited;
|
||||
return getProgressBarPercentage(pageName);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
#progress-bar-container {
|
||||
padding: 0.75rem 0;
|
||||
width: 100%;
|
||||
|
||||
progress {
|
||||
height: 10px;
|
||||
width: 100%;
|
||||
|
||||
/* Firefox */
|
||||
appearance: none;
|
||||
background-color: $blue-100;
|
||||
border: 0;
|
||||
border-radius: $border-radius-pill;
|
||||
box-shadow: $box-shadow-inset;
|
||||
|
||||
&::-moz-progress-bar {
|
||||
background-color: $blue;
|
||||
border-radius: $border-radius-pill;
|
||||
}
|
||||
|
||||
/* Webkit */
|
||||
-webkit-appearance: none;
|
||||
&::-webkit-progress-bar {
|
||||
background-color: $blue-100;
|
||||
border-radius: $border-radius-pill;
|
||||
box-shadow: $box-shadow-inset;
|
||||
}
|
||||
&::-webkit-progress-value {
|
||||
background-color: $blue;
|
||||
border-radius: $border-radius-pill;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -72,6 +72,7 @@ export class Logger {
|
|||
applicationConfig.CONSUMER_CF_DISTRO + applicationConfig.FRONTEND_LOGGER_PATH;
|
||||
const headers = {
|
||||
[headerKeys.EXPERIMENT]: JSON.stringify(store.getters.experimentSettings),
|
||||
[headerKeys.APPLICATION_NAME]: applicationConfig.APPLICATION_NAME,
|
||||
};
|
||||
|
||||
axios({
|
||||
|
|
|
|||
|
|
@ -182,39 +182,6 @@ describe("address-lookup.vue", () => {
|
|||
// Assert
|
||||
expect(wrapper.findComponent({ ref: "alertVinNotFound" }).isVisible()).toBe(true);
|
||||
});
|
||||
test("should show the AlertNoService when displayNoServiceAlert is true", async () => {
|
||||
const { wrapper } = setupMocks({
|
||||
isZipServiceable: true,
|
||||
displayNoServiceAlert: true,
|
||||
lookupVinbyAddressResponse: {
|
||||
isStatePermissible: true,
|
||||
vinVehicles: [], // Return no vehicles
|
||||
},
|
||||
});
|
||||
|
||||
// Set displayNoServiceAlert to true
|
||||
await wrapper.setData({ displayNoServiceAlert: true });
|
||||
|
||||
// Check if the AlertNoService component is rendered
|
||||
const alertNoService = wrapper.findComponent({ ref: "AlertNoService" });
|
||||
expect(alertNoService.exists()).toBe(true);
|
||||
});
|
||||
test("should hide the AlertNoService when displayNoServiceAlert is false", async () => {
|
||||
const { wrapper } = setupMocks({
|
||||
isZipServiceable: true,
|
||||
displayNoServiceAlert: false,
|
||||
lookupVinbyAddressResponse: {
|
||||
isStatePermissible: true,
|
||||
vinVehicles: [], // Return no vehicles
|
||||
},
|
||||
});
|
||||
// Ensure displayNoServiceAlert is false
|
||||
await wrapper.setData({ displayNoServiceAlert: false });
|
||||
|
||||
// Check if the AlertNoService component is not rendered
|
||||
const alertNoService = wrapper.findComponent({ ref: "AlertNoService" });
|
||||
expect(alertNoService.exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("navigation", () => {
|
||||
|
|
@ -774,18 +741,6 @@ function setupMocks({
|
|||
wrapper.vm.setCmsContent = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.removeLoader = jest.fn();
|
||||
const closestShops = {
|
||||
data: {
|
||||
providers: [
|
||||
{ id: 1, name: "Shop 1" },
|
||||
{ id: 2, name: "Shop 2" },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
wrapper.vm.findClosestApplicableShops = jest.fn().mockImplementation(() => {
|
||||
return new Promise((resolve) => resolve(closestShops));
|
||||
});
|
||||
|
||||
return { wrapper };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,20 +81,13 @@
|
|||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<alert
|
||||
ref="AlertNoService"
|
||||
v-if="displayNoServiceAlert"
|
||||
class="my-4"
|
||||
cmsWidgetName="AlertNoServiceWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isDisabled="!meta.valid"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
@back-clicked="backButtonAction"
|
||||
:isForwardActionDisabled="!meta.valid || displayNoServiceAlert" />
|
||||
:isForwardActionDisabled="!meta.valid" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -172,7 +165,7 @@ export default {
|
|||
},
|
||||
serviceZipCode: this.getServiceZipFromStore(),
|
||||
carId: this.getCarIdfromStore(),
|
||||
isHeavyTruck: this.getIsVehicleHeavyTruckFromStore(),
|
||||
isHeavyTruck: this.getIsVehicleHeavyTruckServiceableFromStore(),
|
||||
displayNonServiceableZipAlert: false,
|
||||
displayVinNotFoundAlert: false,
|
||||
displayMatchedDifferentVehicleAlert: false,
|
||||
|
|
@ -184,7 +177,6 @@ export default {
|
|||
displayInvalidZipAlert: false,
|
||||
showServiceZipField: this.getServiceZipFromStore(),
|
||||
isZipServiceable: false,
|
||||
displayNoServiceAlert: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -232,8 +224,8 @@ export default {
|
|||
getCarIdfromStore() {
|
||||
return store.getters.vehicle.carId;
|
||||
},
|
||||
getIsVehicleHeavyTruckFromStore() {
|
||||
return store.getters.isHeavyTruckVehicle;
|
||||
getIsVehicleHeavyTruckServiceableFromStore() {
|
||||
return store.getters.vehicle.isBigTruck && store.getters.vehicle.canSafeliteService;
|
||||
},
|
||||
async findClosestApplicableShops() {
|
||||
return await this.dispatchStoreActionWithLogging(
|
||||
|
|
@ -301,17 +293,6 @@ export default {
|
|||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
this.displayInvalidZipAlert = false;
|
||||
if (this.serviceZipCode != null && this.isHeavyTruck) {
|
||||
const closestShops = await this.findClosestApplicableShops(
|
||||
this.serviceZipCode,
|
||||
this.carId
|
||||
);
|
||||
this.displayNoServiceAlert = !closestShops?.data?.providers?.length;
|
||||
if (this.displayNoServiceAlert) {
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
}
|
||||
|
||||
const carsFound = resultMap.vinLookupResponse.vinVehicles;
|
||||
|
||||
// Handle cases for different amounts of VINS found for the address.
|
||||
|
|
@ -463,7 +444,6 @@ export default {
|
|||
this.displayNonServiceableZipAlert = false;
|
||||
this.displayMatchedDifferentVehicleAlert = false;
|
||||
this.displayVinLookupByHomeAddressNotAllowedAlert = false;
|
||||
this.displayNoServiceAlert = false;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
|
@ -515,7 +495,6 @@ export default {
|
|||
handler(newValue) {
|
||||
// If they modify the service zip code, then hide the error message.
|
||||
this.displayNonServiceableZipAlert = false;
|
||||
this.displayNoServiceAlert = false;
|
||||
},
|
||||
},
|
||||
showServiceZipField: {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,10 @@
|
|||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
|
||||
<textBlock cmsWidgetName="DisclaimerCopyWidget" typeStyle="caption" />
|
||||
<textBlock
|
||||
class="mb-5"
|
||||
cmsWidgetName="DisclaimerCopyWidget"
|
||||
typeStyle="caption" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -203,3 +203,9 @@ export default {
|
|||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ui-autocomplete {
|
||||
z-index: 3;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -611,42 +611,6 @@ describe("license-plate-lookup.vue", () => {
|
|||
expect(arePagePrerequisitesValid).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("alerts", () => {
|
||||
test("should show the AlertNoService when displayNoServiceAlert is true", async () => {
|
||||
const { wrapper } = setupMocks({
|
||||
isZipServiceable: true,
|
||||
displayNoServiceAlert: true,
|
||||
lookupVinbyAddressResponse: {
|
||||
isStatePermissible: true,
|
||||
vinVehicles: [], // Return no vehicles
|
||||
},
|
||||
});
|
||||
|
||||
// Set displayNoServiceAlert to true
|
||||
await wrapper.setData({ displayNoServiceAlert: true });
|
||||
|
||||
// Check if the AlertNoService component is rendered
|
||||
const alertNoService = wrapper.findComponent({ ref: "AlertNoService" });
|
||||
expect(alertNoService.exists()).toBe(true);
|
||||
});
|
||||
test("should hide the AlertNoService when displayNoServiceAlert is false", async () => {
|
||||
const { wrapper } = setupMocks({
|
||||
isZipServiceable: true,
|
||||
displayNoServiceAlert: false,
|
||||
lookupVinbyAddressResponse: {
|
||||
isStatePermissible: true,
|
||||
vinVehicles: [], // Return no vehicles
|
||||
},
|
||||
});
|
||||
// Ensure displayNoServiceAlert is false
|
||||
await wrapper.setData({ displayNoServiceAlert: false });
|
||||
|
||||
// Check if the AlertNoService component is not rendered
|
||||
const alertNoService = wrapper.findComponent({ ref: "AlertNoService" });
|
||||
expect(alertNoService.exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks({
|
||||
|
|
@ -752,18 +716,5 @@ function setupMocks({
|
|||
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => "");
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.removeLoader = jest.fn();
|
||||
const closestShops = {
|
||||
data: {
|
||||
providers: [
|
||||
{ id: 1, name: "Shop 1" },
|
||||
{ id: 2, name: "Shop 2" },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
wrapper.vm.findClosestApplicableShops = jest.fn().mockImplementation(() => {
|
||||
return new Promise((resolve) => resolve(closestShops));
|
||||
});
|
||||
|
||||
return { wrapper, apiPromise };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,6 @@
|
|||
:manualCopy="AlertMatchedDifferentVehicleBody"
|
||||
alertClass="alert-warning"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<alert
|
||||
ref="AlertNoService"
|
||||
v-if="displayNoServiceAlert"
|
||||
|
|
@ -90,7 +89,7 @@
|
|||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
@ForwardClicked="forwardButtonAction || displayNoServiceAlert" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -167,7 +166,7 @@ export default {
|
|||
emailOrSms: this.getEmailOrSmsFromStore(),
|
||||
serviceZipCode: this.getServiceZipFromStore(),
|
||||
carId: this.getCarIdfromStore(),
|
||||
isHeavyTruck: this.getIsVehicleHeavyTruckFromStore(),
|
||||
isHeavyTruck: this.getIsVehicleHeavyTruckServiceableFromStore(),
|
||||
displayNonServiceableZipAlert: false,
|
||||
displayVinNotFoundAlert: false,
|
||||
displayMatchedDifferentVehicleAlert: false,
|
||||
|
|
@ -178,7 +177,6 @@ export default {
|
|||
displayInvalidZipAlert: false,
|
||||
showServiceZipField: this.getServiceZipFromStore(),
|
||||
isZipServiceable: false,
|
||||
displayNoServiceAlert: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -214,8 +212,8 @@ export default {
|
|||
getCarIdfromStore() {
|
||||
return store.getters.vehicle.carId;
|
||||
},
|
||||
getIsVehicleHeavyTruckFromStore() {
|
||||
return store.getters.isHeavyTruckVehicle;
|
||||
getIsVehicleHeavyTruckServiceableFromStore() {
|
||||
return store.getters.vehicle.isBigTruck && store.getters.vehicle.canSafeliteService;
|
||||
},
|
||||
async findClosestApplicableShops() {
|
||||
return await this.dispatchStoreActionWithLogging(
|
||||
|
|
@ -281,17 +279,6 @@ export default {
|
|||
this.displayInvalidZipAlert = true;
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
if (this.isHeavyTruck) {
|
||||
const closestShops = await this.findClosestApplicableShops(
|
||||
this.serviceZipCode,
|
||||
this.carId
|
||||
);
|
||||
this.displayNoServiceAlert = !closestShops?.data?.providers?.length;
|
||||
if (this.displayNoServiceAlert) {
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the CarId has changed.
|
||||
this.isCarIdDifferent =
|
||||
vinLookup.data.vehicle.carId !== this.$store.getters.vehicle.carId;
|
||||
|
|
@ -406,7 +393,6 @@ export default {
|
|||
this.displayMatchedDifferentVehicleAlert = false;
|
||||
this.displayVinLookupByHomeAddressNotAllowedAlert = false;
|
||||
this.displayInvalidZipAlert = false;
|
||||
this.displayNoServiceAlert = false;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
|
@ -451,7 +437,6 @@ export default {
|
|||
serviceZipCode() {
|
||||
// If they modify the service zip code, then hide the error message.
|
||||
this.displayNonServiceableZipAlert = false;
|
||||
this.displayNoServiceAlert = false;
|
||||
this.$refs.navbar.updateButtonText(
|
||||
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,259 @@
|
|||
<template>
|
||||
<div class="alert fade show my-2 py-2 border-0 alert-info" role="alert">
|
||||
<component :is="'script'" src="https://js.afterpay.com/afterpay-1.x.js" async></component>
|
||||
<div class="my-4 alert-heading text-center">
|
||||
<span v-html="afterpayHeaderCopy" />
|
||||
|
||||
<span class="afterpay-amount">{{ this.afterpayPrice }}</span>
|
||||
<br />
|
||||
<span class="alert-sub-heading" v-html="afterpayHeaderSubHeaderCopy" />
|
||||
|
||||
<img :src="afterPayImageUrl" />
|
||||
|
||||
<a
|
||||
id="afterpay-learnmore"
|
||||
href="#"
|
||||
data-afterpay-modal="en_US-safelite"
|
||||
data-bind="click:afterpayLearnMore">
|
||||
<img :src="infoIcon" alt="Info Icon" class="info-icon" />
|
||||
</a>
|
||||
<div class="after-pay">
|
||||
<div
|
||||
class="after-pay-toggle"
|
||||
:class="[isExpanded ? 'expanded' : '']"
|
||||
@click="toggleIsExpanded">
|
||||
<a href="#!" v-html="paymentPlanToggleHeading" />
|
||||
</div>
|
||||
<div class="after-pay-details">
|
||||
<div class="payment-section">
|
||||
<div class="payment-card">
|
||||
<div class="calendar-section">
|
||||
<div class="icon"><img :src="calendarIcon" /></div>
|
||||
<div>Today</div>
|
||||
</div>
|
||||
<div class="payment-amount-section">
|
||||
<div class="icon"><img :src="paymentIcon" /></div>
|
||||
<div class="amount-due">{{ this.afterpayPrice }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="payment-card">
|
||||
<div class="calendar-section">
|
||||
<div class="icon"><img :src="calendarIcon" /></div>
|
||||
<div>2 weeks</div>
|
||||
</div>
|
||||
<div class="payment-amount-section">
|
||||
<div class="icon"><img :src="paymentIcon" /></div>
|
||||
<div class="amount-due">{{ this.afterpayPrice }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="payment-card">
|
||||
<div class="calendar-section">
|
||||
<div class="icon"><img :src="calendarIcon" /></div>
|
||||
<div>4 weeks</div>
|
||||
</div>
|
||||
<div class="payment-amount-section">
|
||||
<div class="icon"><img :src="paymentIcon" /></div>
|
||||
<div class="amount-due">{{ this.afterpayPrice }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="payment-card">
|
||||
<div class="calendar-section">
|
||||
<div class="icon"><img :src="calendarIcon" /></div>
|
||||
<div>6 weeks</div>
|
||||
</div>
|
||||
<div class="payment-amount-section">
|
||||
<div class="icon"><img :src="paymentIcon" /></div>
|
||||
<div class="amount-due">{{ this.afterpayPrice }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="legend">
|
||||
<div class="legend-item">
|
||||
<img :src="calendarKeyIcon" />
|
||||
<span>Payment Schedule</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<img :src="paymentKeyIcon" />
|
||||
<span>Amount Due</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "afterpay-breakout",
|
||||
|
||||
props: {
|
||||
cmsWidgetName: String,
|
||||
totalAmount: Number,
|
||||
},
|
||||
data() {
|
||||
return { isExpanded: false };
|
||||
},
|
||||
methods: {
|
||||
toggleIsExpanded() {
|
||||
this.isExpanded = !this.isExpanded;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
afterPayImageUrl() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "Image");
|
||||
},
|
||||
hasImage() {
|
||||
return !!this.imageUrl;
|
||||
},
|
||||
afterpayHeaderCopy() {
|
||||
// Text for Afterpay Breakout experiment
|
||||
|
||||
return this.getCmsContent(this.cmsWidgetName, "HeaderText");
|
||||
},
|
||||
afterpayHeaderSubHeaderCopy() {
|
||||
// Text for Afterpay Breakout experiment
|
||||
|
||||
return this.getCmsContent(this.cmsWidgetName, "SubheaderText");
|
||||
},
|
||||
afterpayPrice() {
|
||||
const adjusted = (this.totalAmount / 4).toFixed(2);
|
||||
return `$${adjusted}`;
|
||||
},
|
||||
infoIcon() {
|
||||
return require(`@/assets/img/icons/info-circle.svg`);
|
||||
},
|
||||
calendarIcon() {
|
||||
return require(`@/assets/img/icons/calendar-icon-black.svg`);
|
||||
},
|
||||
paymentIcon() {
|
||||
return require(`@/assets/img/icons/payment-icon-green.svg`);
|
||||
},
|
||||
calendarKeyIcon() {
|
||||
return require(`@/assets/img/icons/calendar-icon-grey.svg`);
|
||||
},
|
||||
paymentKeyIcon() {
|
||||
return require(`@/assets/img/icons/payment-icon-grey.svg`);
|
||||
},
|
||||
paymentPlanToggleHeading() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "BodyText");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.alert {
|
||||
border-radius: 5px;
|
||||
margin-top: 1rem;
|
||||
|
||||
&.alert-info {
|
||||
.alert-heading {
|
||||
color: #000000;
|
||||
font-weight: 600;
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
@include media-breakpoint-up(md) {
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.afterpay-amount {
|
||||
color: #0c7e47;
|
||||
}
|
||||
.alert-sub-heading {
|
||||
color: #4b4c4e;
|
||||
font-weight: 400;
|
||||
}
|
||||
.after-pay {
|
||||
.after-pay-toggle {
|
||||
cursor: pointer;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
transition: all 0.5s ease;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.9' xml:space='preserve'%3e%3cpath d='M8 8.9c-.2 0-.5-.1-.6-.3L.3 1.5C.1 1.4 0 1.1 0 .9 0 .7.1.4.3.3.4.1.7 0 .9 0c.2 0 .5.1.6.3L8 6.7 14.5.2c.1-.1.4-.2.6-.2.2 0 .5.1.6.3s.3.4.3.6c0 .2-.1.5-.3.6L8.6 8.6c-.1.2-.4.3-.6.3z' fill='%231474a2'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right center;
|
||||
margin-left: 0.5rem;
|
||||
width: 1rem;
|
||||
height: 9px;
|
||||
display: inline-flex;
|
||||
}
|
||||
&.expanded:after {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
&.expanded + .after-pay-details {
|
||||
max-height: 500px;
|
||||
transition: all 250ms ease-in;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.after-pay-toggle a {
|
||||
font-weight: 600;
|
||||
color: #0070d1;
|
||||
text-decoration: none;
|
||||
}
|
||||
.after-pay-details {
|
||||
max-height: 0;
|
||||
transition: all 250ms ease-out;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
.payment-section {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
@include media-breakpoint-up(md) {
|
||||
gap: 0rem;
|
||||
}
|
||||
}
|
||||
.payment-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 6px;
|
||||
border: 0.25px #b3b4b5;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.calendar-section {
|
||||
background-color: #fff; /* White background for calendar area */
|
||||
padding: 0.5rem;
|
||||
text-align: center;
|
||||
gap: 2px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.payment-amount-section {
|
||||
background-color: #e0f7e9; /* Light green background for payment area */
|
||||
padding: 0.5rem;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.amount-due {
|
||||
color: #00723e; /* Green text */
|
||||
}
|
||||
.legend {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 1rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.legend-item span {
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -117,6 +117,12 @@ function setupMocks() {
|
|||
|
||||
return "false";
|
||||
}),
|
||||
hasSettingEqualTo: jest.fn((settingName) => {
|
||||
if (settingName === experimentSettings.DISPLAY_AFTERPAY_BREAKOUT_DISPLAY) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
getCmsContent: jest.fn().mockImplementation(() => {
|
||||
return "test text";
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@
|
|||
:isExpandedOnLoad="false"
|
||||
:isMSRFeeApplicable="isMSRFeeApplicable" />
|
||||
|
||||
<afterpayBreakout
|
||||
v-if="isAfterpayBreakoutDisplay"
|
||||
cmsWidgetName="AfterpayBreakoutAlertWidget"
|
||||
:totalAmount="totalAmountDue" />
|
||||
<hr />
|
||||
|
||||
<div>
|
||||
|
|
@ -118,6 +122,7 @@ import alert from "@/ux-components/alert/alert";
|
|||
import textBlock from "@/digital-components/text-block/text-block";
|
||||
import checkboxQuestion from "@/digital-components/checkbox-question/checkbox-question";
|
||||
import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal";
|
||||
import afterpayBreakout from "@/layouts/payment-method/afterpay-breakout/afterpay-breakout";
|
||||
|
||||
// Supporting Items
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
|
|
@ -131,7 +136,6 @@ import {
|
|||
} from "@/helpers/cms-content-helper";
|
||||
import { paymentMethods } from "@/constants/payment-method-constants";
|
||||
import { experimentSettings } from "@/constants/experiments";
|
||||
import experimentMixin from "@/mixins/experiment-mixin.js";
|
||||
import {
|
||||
revalidatePromosAndValidateQueryStringPromo,
|
||||
buildToastMessagesFromRevalidateOrValidatePromoResponse,
|
||||
|
|
@ -805,6 +809,18 @@ export default {
|
|||
lineItemsCloneForWatcher() {
|
||||
return Object.assign({}, this.lineItems);
|
||||
},
|
||||
isRecalPriceRemove() {
|
||||
return (
|
||||
this.getSettingValue(experimentSettings.RECAL_PRICE_REMOVE)?.toLowerCase() ===
|
||||
"true"
|
||||
);
|
||||
},
|
||||
isAfterpayBreakoutDisplay() {
|
||||
return (
|
||||
(!this.isRecalPriceRemove || !this.isRecalibrationOnOrder) &&
|
||||
this.hasSettingEqualTo(experimentSettings.DISPLAY_AFTERPAY_BREAKOUT_DISPLAY, "true")
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
customCtaCopy(newValue) {
|
||||
|
|
@ -856,6 +872,7 @@ export default {
|
|||
textBlock,
|
||||
checkboxQuestion,
|
||||
contentGroupModal,
|
||||
afterpayBreakout,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -275,8 +275,8 @@ export default {
|
|||
const emailFromStore = store.getters.order.customer.emailAddress;
|
||||
const isEmailInStoreOnPageLoad = emailFromStore?.length > 0;
|
||||
const isPopupSkipped =
|
||||
store.getters.pageData(fmgPageValues.QUOTE).saveProgressPopupSkipped === true;
|
||||
const showSaveProgressPopup = isEmailInStoreOnPageLoad || isPopupSkipped ? false : true;
|
||||
store.getters.pageData(fmgPageValues.QUOTE)?.saveProgressPopupSkipped === true;
|
||||
const showSaveProgressPopup = !(isEmailInStoreOnPageLoad || isPopupSkipped);
|
||||
const showSaveProgressModal = isEmailInStoreOnPageLoad ? false : true;
|
||||
const shouldSkipToInsurance =
|
||||
getBoolFromString(
|
||||
|
|
@ -891,7 +891,7 @@ export default {
|
|||
|
||||
&.funnel-header-wrapper,
|
||||
&.save-progress-popup {
|
||||
display: block;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
marginTopSizeOverride="1" />
|
||||
</template>
|
||||
<locationAlerts cmsWidgetPrefix="LocationAlert-" ref="locationAlerts" />
|
||||
<datePickerForPricingByDay
|
||||
<datePicker
|
||||
customComponentId="dateQuestion"
|
||||
selectableDatesSetting="custom"
|
||||
ref="datePicker"
|
||||
|
|
@ -77,7 +77,7 @@ import navbar from "@/fmg-components/nav-bar/nav-bar";
|
|||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
import { Form, defineRule } from "vee-validate";
|
||||
import datePickerForPricingByDay from "@/experiment-components/date-picker-for-pricing-by-day";
|
||||
import datePicker from "@/digital-components/date-picker/date-picker";
|
||||
import locationAlerts from "@/layouts/schedule/location-alerts/location-alerts";
|
||||
import timeSlotModalQuestion from "./time-slot-modal-question/time-slot-modal-question";
|
||||
import textBlock from "@/digital-components/text-block/text-block";
|
||||
|
|
@ -298,14 +298,13 @@ export default {
|
|||
);
|
||||
|
||||
// While Pricing By Day Experiment is active, using the updated datePicker
|
||||
const datePickerInitialDataPromise =
|
||||
await datePickerForPricingByDay.methods.loadInitialData({
|
||||
// setup config options for date-picker
|
||||
selectableDatesSetting: "custom",
|
||||
initialViewRowsToShow: 5,
|
||||
customSelectableDatesCallback: getAvailableDates,
|
||||
preSelectedDate: preSelectedDate,
|
||||
});
|
||||
const datePickerInitialDataPromise = await datePicker.methods.loadInitialData({
|
||||
// setup config options for date-picker
|
||||
selectableDatesSetting: "custom",
|
||||
initialViewRowsToShow: 5,
|
||||
customSelectableDatesCallback: getAvailableDates,
|
||||
preSelectedDate: preSelectedDate,
|
||||
});
|
||||
|
||||
// Get pricingByDayUpcharge needed for Pricing By Day
|
||||
const pricingByDayUpchargePartPromise = showPricingByDay
|
||||
|
|
@ -735,7 +734,7 @@ export default {
|
|||
funnelSubHeader,
|
||||
Form,
|
||||
loadingModal,
|
||||
datePickerForPricingByDay,
|
||||
datePicker,
|
||||
locationAlerts,
|
||||
timeSlotModalQuestion,
|
||||
textBlock,
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ describe("appointment-type-question.vue", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it("Should display only the In-Shop answer when only in-shop service is available", async () => {
|
||||
it("Should display only the In-Shop and Drop-Off answers when only in-shop service is available", async () => {
|
||||
// Arrange/Act
|
||||
const { wrapper } = setupMocks({
|
||||
mixins: [mockMixin],
|
||||
|
|
@ -108,7 +108,6 @@ describe("appointment-type-question.vue", () => {
|
|||
cmsWidgetName: cmsWidgetName,
|
||||
isServiceableInshop: true,
|
||||
isServiceableMobile: false,
|
||||
isServiceableDropoff: false,
|
||||
},
|
||||
mountOptions: {
|
||||
attachTo: document.body,
|
||||
|
|
@ -124,6 +123,13 @@ describe("appointment-type-question.vue", () => {
|
|||
SubWidgetName: "",
|
||||
Text: "In-shop",
|
||||
},
|
||||
{
|
||||
AnswerImageUrl: "",
|
||||
Name: "Dropoff",
|
||||
SubText: "",
|
||||
SubWidgetName: "",
|
||||
Text: "Drop-off",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ export default {
|
|||
cmsWidgetName: String,
|
||||
isServiceableMobile: Boolean,
|
||||
isServiceableInshop: Boolean,
|
||||
isServiceableDropoff: Boolean,
|
||||
mobileFeeApplies: Boolean,
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -46,7 +45,7 @@ export default {
|
|||
const shouldShowMobile = this.isServiceableMobile;
|
||||
const shouldShowInshop = this.isServiceableInshop;
|
||||
const shouldShowDropoff =
|
||||
this.isServiceableDropoff && !this.$store.getters.damage.isRepair;
|
||||
this.isServiceableInshop && !this.$store.getters.damage.isRepair;
|
||||
|
||||
var answers = this.answersFromCms
|
||||
? this.answersFromCms.filter((answer) => {
|
||||
|
|
@ -78,11 +77,6 @@ export default {
|
|||
isMobileOnly() {
|
||||
return this.isServiceableMobile && !this.isServiceableInshop;
|
||||
},
|
||||
isInshopOnly() {
|
||||
return (
|
||||
this.isServiceableInshop && !this.isServiceableMobile && !this.isServiceableDropoff
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
answersToDisplay: {
|
||||
|
|
@ -92,7 +86,7 @@ export default {
|
|||
newValue.length == 1 &&
|
||||
newValue.findIndex((answer) => answer.Name == "Mobile") != -1
|
||||
) {
|
||||
this.selectedValue = "Mobile";
|
||||
this.selectedValues = "Mobile";
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
|
|
@ -100,14 +94,7 @@ export default {
|
|||
isMobileOnly: {
|
||||
handler(newValue) {
|
||||
if (newValue) {
|
||||
this.selectedValue = "Mobile";
|
||||
}
|
||||
},
|
||||
},
|
||||
isInshopOnly: {
|
||||
handler(newValue) {
|
||||
if (newValue) {
|
||||
this.selectedValue = "Inshop";
|
||||
this.selectedValues = "Mobile";
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -76,7 +76,6 @@
|
|||
v-show="isAppointmentTypeDisplayed"
|
||||
:isServiceableMobile="isServiceableMobile"
|
||||
:isServiceableInshop="isServiceableInshop"
|
||||
:isServiceableDropoff="isServiceableDropoff"
|
||||
:isDisplayed="isAppointmentTypeDisplayed"
|
||||
:mobileFeeApplies="mobileFeeApplies"
|
||||
ref="appointmentTypeQuestion"
|
||||
|
|
@ -197,8 +196,6 @@ export default {
|
|||
isVehicleProtected: this.getIsVehicleProtectedFromStore(),
|
||||
isGlassServiceableInshop: null,
|
||||
isRecalibrationServiceableInshop: null,
|
||||
isGlassServiceableDropoff: null,
|
||||
isRecalibrationServiceableDropoff: null,
|
||||
isGlassServiceableMobile: null,
|
||||
isRecalibrationServiceableMobile: null,
|
||||
selectedAppointmentType: this.getSelectedAppointmentType(),
|
||||
|
|
@ -357,13 +354,6 @@ export default {
|
|||
return this.isGlassServiceableInshop;
|
||||
}
|
||||
},
|
||||
isServiceableDropoff() {
|
||||
if (this.isRecalibrationServiceableDropoff !== null) {
|
||||
return this.isGlassServiceableDropoff && this.isRecalibrationServiceableDropoff;
|
||||
} else {
|
||||
return this.isGlassServiceableDropoff;
|
||||
}
|
||||
},
|
||||
isShopQuestionDisplayed() {
|
||||
return (
|
||||
this.selectedAppointmentType === "Inshop" ||
|
||||
|
|
@ -569,9 +559,6 @@ export default {
|
|||
this.isGlassServiceableInshop = serviceabilityDetails.isGlassServiceableInshop;
|
||||
this.isRecalibrationServiceableInshop =
|
||||
serviceabilityDetails.isRecalibrationServiceableInshop;
|
||||
this.isGlassServiceableDropoff = serviceabilityDetails.isGlassServiceableDropoff;
|
||||
this.isRecalibrationServiceableDropoff =
|
||||
serviceabilityDetails.isRecalibrationServiceableDropoff;
|
||||
this.isGlassServiceableMobile = serviceabilityDetails.isGlassServiceableMobile;
|
||||
this.isRecalibrationServiceableMobile =
|
||||
serviceabilityDetails.isRecalibrationServiceableMobile;
|
||||
|
|
|
|||
|
|
@ -415,33 +415,6 @@ describe("service-zip.vue", () => {
|
|||
expect(wrapper.vm.displayInvalidZipAlert).toBe(false);
|
||||
expect(wrapper.vm.displayNonServiceableZipAlert).toBe(false);
|
||||
});
|
||||
test("should show the AlertNoService when displayNoServiceAlert is true", async () => {
|
||||
// Arrange
|
||||
// no changes to store
|
||||
applyMockStoreDataToGetters();
|
||||
|
||||
const wrapper = setupMocks({});
|
||||
wrapper.vm.serviceZipCode = "12345";
|
||||
|
||||
// Act
|
||||
wrapper.vm.displayNoServiceAlert = true;
|
||||
|
||||
// Check if the AlertNoService component is rendered
|
||||
expect(wrapper.vm.displayNoServiceAlert).toBe(true);
|
||||
});
|
||||
test("should hide the AlertNoService when displayNoServiceAlert is false", async () => {
|
||||
// Arrange
|
||||
// no changes to store
|
||||
applyMockStoreDataToGetters();
|
||||
|
||||
const wrapper = setupMocks({});
|
||||
wrapper.vm.serviceZipCode = "12345";
|
||||
//ACT
|
||||
wrapper.vm.displayNoServiceAlert = false;
|
||||
|
||||
// Check if the AlertNoService component is rendered
|
||||
expect(wrapper.vm.displayNoServiceAlert).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -538,16 +511,5 @@ function setupMocks({ customMountOptions, customZipQuery, customZipDataResponse
|
|||
];
|
||||
|
||||
const wrapper = shallowMount(serviceZip, mountOptions);
|
||||
wrapper.vm.findClosestApplicableShops = jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
providers: [
|
||||
{ id: 1, name: "Shop 1" },
|
||||
{ id: 2, name: "Shop 2" },
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,18 +47,10 @@
|
|||
v-if="displayNonServiceableZipAlert"
|
||||
alertClass="alert-danger" />
|
||||
|
||||
<alert
|
||||
ref="AlertNoService"
|
||||
v-if="displayNoServiceAlert"
|
||||
class="my-4"
|
||||
cmsWidgetName="AlertNoServiceWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="!meta.valid || displayNoServiceAlert"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
|
|
@ -119,10 +111,9 @@ export default {
|
|||
serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode,
|
||||
emailOrSms: this.getEmailOrSmsFromStore(),
|
||||
carId: this.getCarIdfromStore(),
|
||||
isHeavyTruck: this.getIsVehicleHeavyTruckFromStore(),
|
||||
isHeavyTruck: this.getIsVehicleHeavyTruckServiceableFromStore(),
|
||||
displayInvalidZipAlert: false,
|
||||
displayNonServiceableZipAlert: false,
|
||||
displayNoServiceAlert: false,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -198,8 +189,8 @@ export default {
|
|||
getCarIdfromStore() {
|
||||
return store.getters.vehicle.carId;
|
||||
},
|
||||
getIsVehicleHeavyTruckFromStore() {
|
||||
return store.getters.isHeavyTruckVehicle;
|
||||
getIsVehicleHeavyTruckServiceableFromStore() {
|
||||
return store.getters.vehicle.isBigTruck && store.getters.vehicle.canSafeliteService;
|
||||
},
|
||||
async findClosestApplicableShops() {
|
||||
return await this.dispatchStoreActionWithLogging(
|
||||
|
|
@ -208,6 +199,7 @@ export default {
|
|||
"service-zip"
|
||||
);
|
||||
},
|
||||
|
||||
async backButtonAction() {
|
||||
const skipVin = await skipVinLookup();
|
||||
// route to move backwards
|
||||
|
|
@ -286,6 +278,16 @@ export default {
|
|||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
}
|
||||
if (this.isHeavyTruck) {
|
||||
const closestShops = await this.findClosestApplicableShops(
|
||||
this.serviceZipCode,
|
||||
this.carId
|
||||
);
|
||||
this.displayNoServiceAlert = !closestShops?.data?.providers?.length;
|
||||
if (this.displayNoServiceAlert) {
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
}
|
||||
|
||||
const payment = this.$store.getters.payment;
|
||||
const policy = this.$store.getters.policy;
|
||||
|
|
@ -344,6 +346,7 @@ export default {
|
|||
serviceZipCode() {
|
||||
this.displayNonServiceableZipAlert = false;
|
||||
this.displayNoServiceAlert = false;
|
||||
this.displayNoServiceAlert = false;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
|
|
|||
|
|
@ -258,39 +258,6 @@ describe("vin-lookup.vue", () => {
|
|||
// Assert
|
||||
expect(wrapper.findAllComponents({ name: "alert" }).length).toBe(1);
|
||||
});
|
||||
|
||||
test("should show the AlertNoService when displayNoServiceAlert is true", async () => {
|
||||
const { wrapper } = setupMocks({
|
||||
isZipServiceable: true,
|
||||
displayNoServiceAlert: true,
|
||||
lookupVinbyAddressResponse: {
|
||||
isStatePermissible: true,
|
||||
vinVehicles: [], // Return no vehicles
|
||||
},
|
||||
});
|
||||
// Set displayNoServiceAlert to true
|
||||
await wrapper.setData({ displayNoServiceAlert: true });
|
||||
|
||||
// Check if the AlertNoService component is rendered
|
||||
const alertNoService = wrapper.findComponent({ ref: "AlertNoService" });
|
||||
expect(alertNoService.exists()).toBe(true);
|
||||
});
|
||||
test("should hide the AlertNoService when displayNoServiceAlert is false", async () => {
|
||||
const { wrapper } = setupMocks({
|
||||
isZipServiceable: true,
|
||||
displayNoServiceAlert: false,
|
||||
lookupVinbyAddressResponse: {
|
||||
isStatePermissible: true,
|
||||
vinVehicles: [], // Return no vehicles
|
||||
},
|
||||
});
|
||||
// Ensure displayNoServiceAlert is false
|
||||
await wrapper.setData({ displayNoServiceAlert: false });
|
||||
|
||||
// Check if the AlertNoService component is not rendered
|
||||
const alertNoService = wrapper.findComponent({ ref: "AlertNoService" });
|
||||
expect(alertNoService.exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getVinFromImage", () => {
|
||||
|
|
|
|||
|
|
@ -71,8 +71,7 @@
|
|||
vinPopulatedOnPageLoad &&
|
||||
isInsuranceVerified &&
|
||||
!displayInvalidZipAlert &&
|
||||
!displayNonServiceableZipAlert &&
|
||||
!displayNoServiceAlert
|
||||
!displayNonServiceableZipAlert
|
||||
"
|
||||
alertClass="alert-success" />
|
||||
|
||||
|
|
@ -108,23 +107,14 @@
|
|||
vinPopulatedOnPageLoad &&
|
||||
!isInsuranceVerified &&
|
||||
!displayInvalidZipAlert &&
|
||||
!displayNonServiceableZipAlert &&
|
||||
!displayNoServiceAlert
|
||||
!displayNonServiceableZipAlert
|
||||
"
|
||||
alertClass="alert-success" />
|
||||
|
||||
<alert
|
||||
ref="AlertNoService"
|
||||
v-if="displayNoServiceAlert"
|
||||
class="my-4"
|
||||
cmsWidgetName="AlertNoServiceWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="!meta.valid || displayNoServiceAlert"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
|
|
@ -208,7 +198,7 @@ export default {
|
|||
serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode,
|
||||
emailOrSms: this.getEmailOrSmsFromStore(),
|
||||
carId: this.getCarIdfromStore(),
|
||||
isHeavyTruck: this.getIsVehicleHeavyTruckFromStore(),
|
||||
isHeavyTruck: this.getIsVehicleHeavyTruckServiceableFromStore(),
|
||||
isCarIdDifferent: false,
|
||||
customAlertData: {},
|
||||
previouslyEnteredCarId: "",
|
||||
|
|
@ -219,7 +209,6 @@ export default {
|
|||
displayVinNotFoundAlert: false,
|
||||
displayMatchedDifferentVehicleAlert: false,
|
||||
displayVinScanFailedAlert: false,
|
||||
displayNoServiceAlert: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -235,12 +224,6 @@ export default {
|
|||
getZipFromStore() {
|
||||
return this.$store.getters.order.serviceLocation.zipCode;
|
||||
},
|
||||
getCarIdfromStore() {
|
||||
return store.getters.vehicle.carId;
|
||||
},
|
||||
getIsVehicleHeavyTruckFromStore() {
|
||||
return store.getters.isHeavyTruckVehicle;
|
||||
},
|
||||
attachCustomEvents() {
|
||||
this.prependActionToMethod(this, this.forwardButtonAction, () => {
|
||||
this.pushEventToGA(
|
||||
|
|
@ -264,6 +247,9 @@ export default {
|
|||
);
|
||||
}
|
||||
},
|
||||
getIsVehicleHeavyTruckServiceableFromStore() {
|
||||
return store.getters.vehicle.isBigTruck && store.getters.vehicle.canSafeliteService;
|
||||
},
|
||||
async findClosestApplicableShops() {
|
||||
return await this.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_CLOSEST_APPLICABLE_SHOPS,
|
||||
|
|
@ -303,18 +289,6 @@ export default {
|
|||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
this.displayInvalidZipAlert = false;
|
||||
if (this.isHeavyTruck) {
|
||||
// If a VIN has already been found. Validate the Service Zip (in case of changes)
|
||||
var closestShops = await this.findClosestApplicableShops(
|
||||
this.serviceZipCode,
|
||||
this.carId
|
||||
);
|
||||
this.displayNoServiceAlert = !closestShops?.data?.providers?.length;
|
||||
if (this.displayNoServiceAlert) {
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
}
|
||||
|
||||
// If either lookup fails, remove the loader and stop processing the page.
|
||||
if (!resultMap.vehicleLookupResponse || !resultMap.zipCodeData.isServiceable) {
|
||||
// If the vehicle result is undefined, the vin entered was invalid.
|
||||
|
|
@ -366,6 +340,18 @@ export default {
|
|||
},
|
||||
false
|
||||
);
|
||||
if (this.isHeavyTruck) {
|
||||
closestShops = await this.findClosestApplicableShops(
|
||||
this.serviceZipCode,
|
||||
this.carId
|
||||
);
|
||||
this.displayNoServiceAlert = !closestShops?.data?.providers?.length;
|
||||
if (this.displayNoServiceAlert) {
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
}
|
||||
|
||||
// if no value due to field being optional, blank both phone and email address
|
||||
|
||||
// if no value due to field being optional, blank both phone and email address
|
||||
if (!this.emailOrSms) {
|
||||
|
|
@ -430,16 +416,6 @@ export default {
|
|||
false
|
||||
);
|
||||
}
|
||||
|
||||
closestShops = await this.findClosestApplicableShops(
|
||||
this.serviceZipCode,
|
||||
this.carId
|
||||
);
|
||||
this.displayNoServiceAlert = !closestShops?.data?.providers?.length;
|
||||
if (this.displayNoServiceAlert) {
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
|
||||
// if no value due to field being optional, blank both phone and email address
|
||||
if (!this.emailOrSms) {
|
||||
await this.dispatchStoreAction(storeActions.SAVE_PHONE_NUMBER, "", false);
|
||||
|
|
@ -523,6 +499,7 @@ export default {
|
|||
this.displayVinNotFoundAlert = false;
|
||||
this.displayVinScanFailedAlert = false;
|
||||
this.displayNoServiceAlert = false;
|
||||
this.displayNoServiceAlert = false;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
|
@ -599,7 +576,6 @@ export default {
|
|||
},
|
||||
serviceZipCode() {
|
||||
this.displayNonServiceableZipAlert = false;
|
||||
this.displayNoServiceAlert = false;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
|
|
|||
Loading…
Reference in a new issue