Merge pull request #2512 from Safelite/rlsmerge/2025.05.22-to-develop

Rlsmerge/2025.05.22 to develop
This commit is contained in:
chloeherdsafelite 2025-05-13 16:27:48 -04:00 committed by GitHub
commit d53b88ee80
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 512 additions and 277 deletions

View file

@ -23,7 +23,6 @@ const GaActions = {
SUBMITTED: "Submitted",
DISPLAYED: "Displayed",
CASH_QUOTE_DISPLAYED: "Cash quote displayed",
SERVICE_PACKAGE_PRICE: "Service package price",
MOBILE_AVAILABLE: "mobile_available",
SHOPS_FIRST_DISPLAYED: "shops_first_displayed",
MORE_LOCATIONS_CLICKED: "more_locations_clicked",

View file

@ -69,6 +69,7 @@ const storeMutations = {
UPDATE_SETTLED_TENDER_AMOUNT: "updateSettledTenderAmount",
UPDATE_IS_RECAL_ACK_OPT_IN: "updateIsRecalAckOptIn",
UPDATE_IS_MSR_FEE_APPLICABLE: "updateIsMSRFeeApplicable",
UPDATE_CASH_PRICE_SUBTOTAL: "updateCashPriceSubTotal",
// EVENT BUS MUTATIONS
ADD_EVENT_TO_BUS: "addEventToBus",

View file

@ -35,4 +35,9 @@ export const inputButtonProps = {
default: false,
},
suppressError: Boolean,
labelBold: {
type: Boolean,
required: false,
default: false,
},
};

View file

@ -9,7 +9,9 @@ https://safelite.atlassian.net/wiki/spaces/DC/pages/76644418/Button+Question+Com
v-if="questionText && answers && answers.length > 0"
class="question-text d-flex"
:class="{ 'small-question-text': isSmallQuestionText }">
<span class="fw-bold w-100">{{ questionText }}</span>
<span class="fw-bold w-100" :class="[labelBold ? 'span-bold' : '']">
{{ questionText }}
</span>
</div>
<div class="w-100 d-flex justify-content-center">
@ -57,6 +59,7 @@ https://safelite.atlassian.net/wiki/spaces/DC/pages/76644418/Button+Question+Com
:setLastValuePushedToGa="setLastValuePushedToGa"
:isInsuranceSelected="isInsuranceSelected"
:suppressError="suppressError"
:labelBold="labelBold"
@buttonEvent="handleButtonEvent"
v-model="selectedValues" />
<!-- For nested questions -->
@ -141,6 +144,11 @@ export default {
default: false,
},
isInsuranceSelected: Boolean,
labelBold: {
type: Boolean,
required: false,
default: false,
},
},
setup(props) {
const propsClone = Object.assign({}, props);
@ -360,6 +368,9 @@ export default {
& > span {
text-align: center;
}
.span-bold {
font-weight: 600;
}
}
.small-question-text {
&.question-text {

View file

@ -1042,6 +1042,9 @@ export default {
min-width: 3rem;
width: 3rem;
}
&:hover {
border: 2px solid $blue;
}
}
}
&.unavailable-day {
@ -1083,7 +1086,6 @@ export default {
}
input[type="radio"]:checked + label:after {
background-color: $black;
top: 2rem;
}
}
}

View file

@ -6,6 +6,7 @@
:for="dropdownId"
:aria-label="questionText"
class="form-label"
:class="[labelBold ? 'label-bold' : '']"
v-html="questionText"></label>
<select
v-model="selectedOption"
@ -61,6 +62,11 @@ export default {
required: false,
default: false,
},
labelBold: {
type: Boolean,
required: false,
default: false,
},
},
setup(props) {
const uuid = uuidv4();
@ -133,6 +139,10 @@ export default {
color: $black;
font-weight: 500;
}
.label-bold {
color: $black;
font-weight: 600;
}
.form-label {
margin-bottom: 0.25rem;
}

View file

@ -119,12 +119,14 @@ export default {
},
onModalOpened() {
this.onModalOpenedCallback?.();
this.$emit("isModalOpened", true);
},
onModalClosed() {
this.resetButtonStyle();
this.onModalClosedCallback?.();
if (this.suppressPageScroll)
document.querySelector("body").classList.remove("prevent-modal-scroll");
this.$emit("isModalOpened", false);
},
openModal() {
if (this.suppressPageScroll)

View file

@ -1,5 +1,7 @@
<template>
<span class="salesforce-webchat"></span>
<span class="salesforce-webchat">
<button v-show="hideSalesforceWebchatLaunchButton" class="salesforce-chat-button"></button>
</span>
</template>
<script>
@ -49,12 +51,121 @@ export default {
};
</script>
<style type="text/css">
.embeddedServiceHelpButton .helpButton .uiButton {
background-color: #167cac;
font-family: "Arial", sans-serif;
<style lang="scss">
.salesforce-webchat {
.salesforce-chat-button {
width: 1.5rem;
height: 1.5rem;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xml:space='preserve' id='Layer_2' x='0' y='0' style='enable-background:new 0 0 24 24' version='1.1' viewBox='0 0 24 24'%3E%3Cstyle%3E .st0%7Bfill:%232e6fb6%7D %3C/style%3E%3Ccircle cx='12' cy='12' r='12' class='st0'/%3E%3Cpath d='M18.3 12c0 3.5-2.8 6.3-6.3 6.3-1.2 0-2.4-.4-3.4-1l-2.3.7s-.5.1-.3-.4.5-1.7.7-2.2c-.6-1-1-2.1-1-3.3 0-3.5 2.8-6.3 6.3-6.3 3.5-.1 6.3 2.7 6.3 6.2z' style='fill:%23fff'/%3E%3Ccircle cx='10.2' cy='12' r='.6' class='st0'/%3E%3Ccircle cx='12' cy='12' r='.6' class='st0'/%3E%3Ccircle cx='13.8' cy='12' r='.6' class='st0'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-size: 1.5rem 1.5rem;
background-position: center;
border: none;
background-color: transparent;
}
}
.embeddedServiceHelpButton .helpButton .uiButton:focus {
outline: 1px solid #167cac;
.dockableContainer {
&.showDockableContainer {
font-family: AvertaRegular;
color: $gray-600;
.sidebarBody {
button {
background: $blue;
color: $white;
&:hover {
background: $blue-700 !important;
}
}
.form-element__help {
color: $red;
}
.embeddedServiceSidebarFormField {
.uiInput {
.uiLabel-left {
color: $gray-600;
}
}
}
.embeddedServiceSidebarButton {
&.uiButton--inverse {
.label {
color: $white;
text-decoration: underline;
}
&:not(:disabled):focus {
background-color: $blue;
}
}
}
.embeddedServiceLiveAgentStateChatAvatar {
&.isLightningOutContext {
.agentIconColor0 {
background-color: $blue;
}
}
}
.embeddedServiceLiveAgentStateChatInputFooter {
.footerMenuWrapper {
.footer-menu-items {
.slds-dropdown__item {
> a {
padding-left: 0;
max-width: 100%;
display: flex;
justify-content: center;
}
}
}
}
}
.embeddedServiceLiveAgentStateChatInputFooter {
.footerMenuWrapper {
.footer-menu-items {
a {
margin: 0 auto;
}
}
}
}
}
.chatHeaderBranding {
background-color: $blue;
}
span {
&.required {
color: $red;
}
}
}
}
.embeddedServiceHelpButton {
.helpButton {
.uiButton {
background-color: $blue !important;
}
}
.embeddedServiceIcon {
display: inline-block !important;
}
}
.modalContainer {
&.sidebarMinimized {
&.layout-docted {
&.embeddedServiceSidebar {
.embeddedServiceSidebarMinimizedDefaultUI {
.helpButton,
.sidebarHeader {
&.minimizedContainer {
.embeddedServiceSidebarMinimizedDefaultUI {
background-color: $blue;
border: 1px solid $blue;
}
}
}
}
}
}
}
}
</style>

View file

@ -13,6 +13,7 @@
:class="[
questionAlignment === 'center' ? 'text-center w-100 mb-5' : '',
hideInput ? 'hide-input' : '',
labelBold ? 'label-bold' : '',
]"
v-html="questionText"></label>
<div
@ -132,6 +133,11 @@ export default {
isRequired: false,
},
autocomplete: String,
labelBold: {
type: Boolean,
required: false,
default: false,
},
},
setup(props) {
const uuid = uuidv4();
@ -251,6 +257,9 @@ export default {
color: $black;
font-weight: 500;
}
.label-bold {
font-weight: 600;
}
.input-wrapper {
position: relative;
&.has-search-icon {

View file

@ -14,7 +14,8 @@
hasIcon
validationRules="street-address-required"
autocomplete="street-address"
@keydown.enter.prevent />
@keydown.enter.prevent
:labelBold="labelBold" />
</div>
</div>
<transition name="fade" mode="out-in">
@ -27,7 +28,8 @@
customInputId="apartmentNumberOrBusinessName"
cmsWidgetName="ApartmentNumberOrBusinessNameQuestionWidget"
v-model="addressModel.apartmentNumberOrBusinessName"
ref="apartmentNumberOrBusinessName" />
ref="apartmentNumberOrBusinessName"
:labelBold="labelBold" />
</div>
</div>
</transition>
@ -40,7 +42,8 @@
v-model="addressModel.city"
ref="city"
validationRules="city-required"
autocomplete="address-level2" />
autocomplete="address-level2"
:labelBold="labelBold" />
</div>
</div>
</transition>
@ -54,7 +57,8 @@
ref="state"
:options="stateOptions"
validationRules="state-required"
autocomplete="address-level1" />
autocomplete="address-level1"
:labelBold="labelBold" />
</div>
<div class="col">
<textboxQuestion
@ -64,7 +68,8 @@
ref="zipCode"
mask="#####"
validationRules="zip-code-required|zip-code-format"
autocomplete="postal-code" />
autocomplete="postal-code"
:labelBold="labelBold" />
</div>
</div>
</transition>
@ -124,6 +129,11 @@ export default {
type: Boolean,
default: false,
},
labelBold: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {

View file

@ -1,13 +1,21 @@
<template>
<div
class="funnel-header d-flex justify-content-center align-items-center flex-column"
v-if="imageSrc">
<!-- HIDDEN FOR 05.08 <salesforceWebchat hideSalesforceWebchatLaunchButton />-->
<img class="logo-image img-fluid" :src="imageSrc" alt="Safelite logo" />
<!-- // 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 class="funnel-header" v-if="imageSrc">
<div class="d-flex justify-content-between align-items-center">
<div class="button-container">
<!-- HIDDEN FOR 05.08 <salesforceWebchat hideSalesforceWebchatLaunchButton /> -->
</div>
<div class="site-logo">
<img class="logo-image img-fluid" :src="imageSrc" alt="Safelite logo" />
</div>
<div class="button-container">
<!-- // 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. -->
</div>
</div>
<div class="d-flex w-100">
<progressBar />
</div>
</div>
<template v-for="globalAlert in globalAlertMessages" :key="globalAlert.id">
<transition name="fade" mode="out-in">
@ -45,7 +53,7 @@ export default {
cmsWidgetName: String,
hideSalesforceWebchatLaunchButton: {
type: Boolean,
default: false,
default: true,
},
},
computed: {
@ -100,16 +108,23 @@ export default {
</script>
<style lang="scss" scoped>
:deep(.menu-modal-container) {
display: none; // This is temporary until the hamburger menu is re-implemented for use with the progress bar
}
.funnel-header {
position: relative;
padding-top: 0.97rem;
}
padding: 1rem 0;
.logo-image {
width: auto;
height: 1.1875rem;
.button-container {
width: 1.5rem;
height: 1.625rem;
}
.logo-image {
width: auto;
height: 1.1875rem;
margin: 0 auto;
}
:deep(.menu-modal-container) {
display: none; // This is temporary until the hamburger menu is re-implemented for use with the progress bar
}
}
</style>

View file

@ -11,21 +11,6 @@
<div class="bar3"></div>
</button>
</div>
<div class="menu-modal-container">
<button
aria-hidden="true"
tabindex="-1"
class="menu-button"
type="button"
:class="[isActive ? 'active' : '']"
@click="toggleModal"
aria-label="Hamburger Menu (modal window)">
<div class="bar1"></div>
<div class="bar2"></div>
<div class="bar3"></div>
</button>
</div>
<!-- Modal -->
<div
class="modal menu-modal fade"
data-bs-backdrop="false"
@ -123,9 +108,6 @@ export default {
<style lang="scss" scoped>
.menu-modal-container {
position: absolute;
padding: 1.47rem 0 1.47rem 1.47rem;
right: 0;
button {
border: none;
&.menu-button {
@ -164,7 +146,7 @@ export default {
}
.modal {
&.menu-modal {
left: auto;
//left: auto;
height: calc(100% - 56px);
top: 56px;
border-top: 1px solid $gray-300;

View file

@ -53,6 +53,7 @@ export default {
cmsWidgetName: String,
lineItems: Array,
isAfterpayBreakoutDisplay: Boolean,
isInsuranceSelected: Boolean,
},
data() {
return {};
@ -72,9 +73,14 @@ export default {
afterpayCopyTokens() {
if (this.isAfterpayBreakoutDisplay) {
// Text for Afterpay Breakout experiment
return splitCopyOnCMSPlaceHolder(
this.getCmsContent(this.cmsWidgetName, "BodyText2")
);
var afterpayBannerCopy = this.getCmsContent(this.cmsWidgetName, "BodyText2");
afterpayBannerCopy =
afterpayBannerCopy &&
afterpayBannerCopy.replaceAll(
"{custom:paymentType}",
this.isInsuranceSelected ? "deductible" : "spending"
);
return splitCopyOnCMSPlaceHolder(afterpayBannerCopy);
} else {
return splitCopyOnCMSPlaceHolder(
this.getCmsContent(this.cmsWidgetName, "HeaderText")

View file

@ -12,8 +12,30 @@ import baseMixin from "@/mixins/base-mixin.js";
import { containsLineItemWithPartType } from "../../helpers/service-package-helper";
jest.mock("@/store", () => ({
commit: jest.fn(),
commit: jest.fn(), // Mock store.commit
dispatch: jest.fn(),
getters: {
order: {
lineItems: {
glassParts: [],
supportingItems: [],
vaps: [],
promos: [],
},
payment: {
insuranceCoverage: {},
isInsurance: false,
parentAccountNumber: 167132,
},
},
payment: {
insuranceCoverage: {},
isInsurance: false,
},
customer: {
emailAddress: "test@test.com",
},
},
}));
// Mock fetchCmsContentForPage
@ -157,6 +179,7 @@ afterEach(() => {
describe("quote.vue", () => {
test("IsInsurance false should navigateWithSaving", async () => {
//Arrange
store.commit = jest.fn(); // Ensure store.commit is mocked
store.getters.payment = {
insuranceCoverage: {},
isInsurance: false,
@ -785,6 +808,7 @@ describe("quote.vue", () => {
});
test("On forward button action save promos", async () => {
//Arrange
store.commit = jest.fn(); // Ensure store.commit is mocked
store.getters.payment = {
insuranceCoverage: {},
isInsurance: false,
@ -809,7 +833,8 @@ describe("quote.vue", () => {
expect(wrapper.vm.lineItems.promos !== null).toBe(true);
});
test("On forward button action save supporting lineItems", async () => {
//Arrange
// Arrange
store.commit = jest.fn(); // Ensure store.commit is mocked
store.getters.payment = {
insuranceCoverage: {},
isInsurance: false,
@ -820,6 +845,7 @@ describe("quote.vue", () => {
parentAccountNumber: 167132,
},
};
const { wrapper } = setupMocks({
customMountOptions: {
router: {

View file

@ -47,6 +47,7 @@
v-if="showAfterpayBanner"
cmsWidgetName="AfterpayModalWidget"
:isAfterpayBreakoutDisplay="isAfterpayBreakoutDisplay"
:isInsuranceSelected="isInsuranceSelected"
:lineItems="lineItems" />
<!-- If vehicle requires recal AND we are hiding recal pricing info, then show recal disclaimer banner. -->
@ -168,6 +169,7 @@ import {
} from "@/helpers/heritage-integration/cookie-helper";
import { showFmgLoadingModal } from "@/helpers/loading-modal-helper";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { storeMutations } from "@/constants/store-mutations";
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
@ -588,7 +590,7 @@ export default {
},
showAfterpayBanner() {
return (
!this.isInsuranceSelected &&
(this.isAfterpayBreakoutDisplay || !this.isInsuranceSelected) &&
(!this.isRecalibrationOnOrder || !this.shouldHideRecalibration)
);
},
@ -766,6 +768,22 @@ export default {
this.$route
);
}
const lineItems = store.getters.order?.lineItems ?? {};
const combinedLineItems = [
...(lineItems.glassParts || []),
...(lineItems.supportingItems || []),
...(lineItems.vaps || []),
...(lineItems.promos || []),
];
const cashPriceSubtotal = combinedLineItems.length
? baseMixin.methods
.getTotalPriceOfAllLineItemsAndChildParts(combinedLineItems, false)
.toFixed(2)
: "";
store.commit(storeMutations.UPDATE_CASH_PRICE_SUBTOTAL, cashPriceSubtotal);
},
attachCustomEventsForAnalytics() {
this.prependActionToMethod(this, this.forwardButtonAction, () => {
@ -789,8 +807,10 @@ export default {
});
this.$nextTick(() => {
const availablePackageNames = this.$refs.servicePackage?.servicePackageAnswers;
var eventLabel = "";
availablePackageNames?.forEach((tier) => {
var priceLabel = "";
if (availablePackageNames?.length > 1 || availablePackageNames?.length == 1) {
var tier = availablePackageNames[0];
if (tier) {
let lineItemsToPrice = this.availableLineItems;
if (this.isRecalibrationOnOrder && this.shouldHideRecalibration) {
@ -815,14 +835,11 @@ export default {
baseMixin.methods.filterOutFees(lineItemsToPrice)
);
price += this.$refs.servicePackage.getVapsPrice(tier.value);
const formattedPrice = parseFloat(price).toFixed(2);
eventLabel += tier.value + "_" + formattedPrice;
priceLabel = price.toFixed(2);
}
eventLabel += ",";
});
eventLabel = eventLabel.slice(0, -1);
this.pushEventToGA("quote", this.GaActions.SERVICE_PACKAGE_PRICE, eventLabel, true);
}
const subTotalLabel = this.Variables?.CASH_SUBTOTAL;
this.pushVariableToDataLayer?.({ [subTotalLabel]: priceLabel });
});
},
async skip(insuranceSelection, packageSelection) {

View file

@ -21,6 +21,8 @@ import servicePackageRadioExperiment from "@/experiment-components/service-packa
import servicePackageRadioAfterpayExperiment from "@/experiment-components/service-package-radio-for-afterpay";
import { partTypeStrings } from "@/constants/part-type-strings";
import { packageNames } from "@/constants/package-names";
import store from "@/store";
import { storeMutations } from "@/constants/store-mutations";
import {
shouldFrontWipersBeAvailable,
shouldRearWipersBeAvailable,
@ -161,6 +163,7 @@ export default {
isInsuranceSelected: this.isInsuranceSelected,
},
}));
this.$emit("currentNumberOfPackages", modifiedAnswers.length);
return modifiedAnswers;
},

View file

@ -375,6 +375,10 @@ export default {
setTimeout(() => {
this.$refs.datePicker.showAnotherMonth().then((moreSelectableDatesData) => {
this.selectableDatesData = moreSelectableDatesData;
if (selectableDatesData?.days?.length > 0) {
this.selectedDate = selectableDatesData.days[0].date;
}
this.setDisplayWaitList();
});
}, 50);

View file

@ -11,7 +11,8 @@
v-model="selectedValue"
:suppressError="suppressError"
:validationRules="validationRules"
isRequired />
isRequired
:labelBold="labelBold" />
</div>
</transition>
</template>
@ -34,6 +35,12 @@ export default {
isServiceableInshop: Boolean,
isServiceableDropoff: Boolean,
mobileFeeApplies: Boolean,
labelBold: {
type: Boolean,
required: false,
default: false,
},
zipCode: String,
},
computed: {
questionText() {
@ -47,6 +54,7 @@ export default {
const shouldShowInshop = this.isServiceableInshop;
const shouldShowDropoff =
this.isServiceableDropoff && !this.$store.getters.damage.isRepair;
const zipCode = this.zipCode;
var answers = this.answersFromCms
? this.answersFromCms.filter((answer) => {
@ -87,12 +95,15 @@ export default {
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 == "Mobile") != -1
) {
this.selectedValue = "Mobile";
// If there is only one option to display and that option is 'Mobile' or 'Inshop' then select it
if (newValue.length == 1) {
const name = newValue[0].Name;
if (
name === AppointmentTypeStrings.MOBILE ||
name === AppointmentTypeStrings.IN_SHOP
) {
this.selectedValue = name;
}
}
},
immediate: true,
@ -100,14 +111,14 @@ export default {
isMobileOnly: {
handler(newValue) {
if (newValue) {
this.selectedValue = "Mobile";
this.selectedValue = AppointmentTypeStrings.MOBILE;
}
},
},
isInshopOnly: {
handler(newValue) {
if (newValue) {
this.selectedValue = "Inshop";
this.selectedValue = AppointmentTypeStrings.IN_SHOP;
}
},
},

View file

@ -8,6 +8,7 @@ export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) {
}
const zipCodeData = await getZipCodeData(serviceZipCode);
const providerData = await getShopProviderData(serviceZipCode);
// Get the Mobile Fee Part
const mobileFeePart = await baseMixin.methods.dispatchStoreActionWithLogging(
@ -15,6 +16,7 @@ export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) {
{
serviceZipCode: serviceZipCode,
serviceZipCodeCtu: zipCodeData.zipCodeCtu,
mobileProviderNumber: providerData.data.mobileProviderNumber,
},
pageNameToLog,
false

View file

@ -91,6 +91,7 @@ const mockStoreActionPriceOrderItemsAndSaveServerData =
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA;
const mockStoreActionGetServiceabilityDetails = storeActions.GET_SERVICEABILITY_DETAILS;
const mockStoreActionGetShopTimeSlots = storeActions.GET_SHOP_TIME_SLOTS;
const mockStoreActionGetProviders = storeActions.GET_PROVIDERS;
const mockGetShopTimeSlotsGoodAvailability = {
data: {
@ -236,6 +237,15 @@ jest.mock("@/mixins/base-mixin.js", () => ({
return Promise.resolve(mockGetShopTimeSlotsLowAvailability);
}
if (actionName === mockStoreActionGetProviders) {
return Promise.resolve({
data: {
mobileProviderNumber: "001820",
shopProviders: [{}],
},
});
}
}),
},
}));

View file

@ -189,41 +189,7 @@ describe("mobile-location-modal-questions.vue", () => {
expect(wrapper.vm.internalModel.serviceZipCode).toEqual("55555");
});
it("Should open the modal when the 'Enter your service address' link is clicked", async () => {
// Arrange
const mobileLocationQuestions = {
addressQuestions: {
streetAddress: "555 Some St",
apartmentNumberOrBusinessName: "Apt 1",
city: "Funkytown",
state: "OH",
zipCode: "55555",
},
isVehicleProtected: true,
serviceZipCode: "55555",
};
const { wrapper } = setupMocks({
mixins: [mockMixin],
props: {
modelValue: mobileLocationQuestions,
},
mountOptions: {
attachTo: document.body,
},
});
wrapper.vm.$refs.MobileLocationModalWidget.openModal = jest.fn();
const mobileLocationLink = wrapper.findComponent({ ref: "mobileLocationLink" });
// // Act
await mobileLocationLink.trigger("click-event");
// // Assert
expect(wrapper.vm.$refs.MobileLocationModalWidget.openModal).toHaveBeenCalled();
});
it("Should emit setMobileLocationAndProceed on setMobileLocation for a valid address and vehicle protection answer", async () => {
it("Should emit setMobileLocation on setMobileLocation for a valid address and vehicle protection answer", async () => {
// Arrange
const mobileLocationQuestions = {
addressQuestions: {
@ -258,7 +224,6 @@ describe("mobile-location-modal-questions.vue", () => {
},
attachTo: document.body,
});
wrapper.vm.$refs.MobileLocationModalWidget.closeModal = jest.fn();
// Act
wrapper.vm.internalModel = newMobileLocationQuestions;
@ -266,13 +231,13 @@ describe("mobile-location-modal-questions.vue", () => {
await wrapper.vm.setMobileLocation();
// Assert
expect(wrapper.emitted()["setMobileLocationAndProceed"]).toBeTruthy();
expect(wrapper.emitted()["setMobileLocationAndProceed"][0][0]).toStrictEqual(
expect(wrapper.emitted()["setMobileLocation"]).toBeTruthy();
expect(wrapper.emitted()["setMobileLocation"][0][0]).toStrictEqual(
newMobileLocationQuestions
);
});
it("Should not emit setMobileLocationAndProceed on setMobileLocation for an invalid address zip code", async () => {
it("Should not emit setMobileLocation on setMobileLocation for an invalid address zip code", async () => {
// Arrange
const mobileLocationQuestions = {
addressQuestions: {
@ -307,15 +272,13 @@ describe("mobile-location-modal-questions.vue", () => {
},
attachTo: document.body,
});
wrapper.vm.$refs.MobileLocationModalWidget.closeModal = jest.fn();
wrapper.vm.resetModalButtonStyle = jest.fn();
// Act
wrapper.vm.internalModel = newMobileLocationQuestions;
await wrapper.vm.setMobileLocation();
// Assert
expect(wrapper.emitted("setMobileLocationAndProceed")).not.toBeTruthy();
expect(wrapper.emitted("setMobileLocation")).not.toBeTruthy();
});
it("Should display invalid zip alert for invalid address zip inputs", async () => {

View file

@ -1,52 +1,48 @@
<template>
<div class="mobile-location-questions">
<div class="text-center" :id="componentId">
<label
for="mobileLocationLinkPromptId"
:aria-label="mobileLocationLinkPromptText"
class="form-label fw-bold w-100 ps-4 pe-4 pt-4 text-black"
v-html="mobileLocationLinkPromptText"></label>
<div class="update-mobile-location-text-link">
<textLink
ref="mobileLocationLink"
id="mobileLocationLinkPromptId"
linkType="text"
:text="mobileLocationLinkText"
href="#!"
@click-event="openModal" />
<transition name="fade" mode="out-in">
<div class="mobile-location-questions">
<div class="text-center" :id="componentId">
<label
for="mobileLocationLinkPromptId"
:aria-label="mobileLocationLinkPromptText"
class="form-label fw-bold w-100 ps-4 pe-4 pt-4 text-black"
v-html="mobileLocationLinkPromptText"></label>
<div class="update-mobile-location-text-link">
<textLink
ref="mobileLocationLink"
id="mobileLocationLinkPromptId"
linkType="text"
:text="mobileLocationLinkText"
href="#!"
@click-event="toggleMobileLocation" />
</div>
<div v-show="errorMessage" class="row my-1 form-test-error">
<span
class="d-inline-flex small mt-0 center-error-message"
aria-atomic="true"
aria-live="polite">
{{ errorMessage }}
</span>
</div>
<textBlock
v-if="mobileFeeApplies"
:customText="mobileFeeText"
cmsWidgetName="MobileFeeDisclaimerWidget"
typeStyle="caption" />
</div>
<div v-show="errorMessage" class="row my-1 form-test-error">
<span
class="d-inline-flex small mt-0 center-error-message"
aria-atomic="true"
aria-live="polite">
{{ errorMessage }}
</span>
</div>
<textBlock
v-if="mobileFeeApplies"
:customText="mobileFeeText"
cmsWidgetName="MobileFeeDisclaimerWidget"
typeStyle="caption" />
</div>
<modal
:ref="modalName"
:headerText="modalHeaderText"
:footerButtonText="modalFooterText"
:onModalOpenedCallback="onModalOpened"
:onModalClosedCallback="onModalClosed"
@isModalOpened="setModalStatus"
@footer-button-event="setMobileLocation">
<template v-if="isModalOpened">
<div v-if="isMobileLocationOpened" class="address-questions-container">
<div v-html="headerText" class="HeaderText" />
<addressQuestions
ref="addressQuestions"
v-model="internalModel.addressQuestions"
captureApartmentNumberOrBusinessName="true"
preserveCityAndStateOnReset="true" />
preserveCityAndStateOnReset="true"
labelBold="true" />
<vehicleProtectedQuestion
ref="vehicleProtectedQuestion"
v-model="internalModel.isVehicleProtected"
cmsWidgetName="VehicleProtectedQuestionWidget" />
cmsWidgetName="VehicleProtectedQuestionWidget"
labelBold="true" />
<textBlock cmsWidgetName="WorkspaceRequirementsWidget" typeStyle="caption" />
<alert
ref="alertInvalidZip"
@ -62,16 +58,14 @@
cmsWidgetName="AlertMismatchStateAndZipWidget"
alertClass="alert-danger"
v-bind:isDismissible="false" />
</template>
</modal>
</div>
</div>
</div>
</transition>
</template>
<script>
// Components
import textLink from "@/ux-components/text-link/text-link";
import textBlock from "@/digital-components/text-block/text-block";
import modal from "@/digital-components/modal/modal";
import alert from "@/ux-components/alert/alert";
import addressQuestions from "@/fmg-components/address-questions/address-questions";
import vehicleProtectedQuestion from "@/layouts/service-location/mobile-location-modal-questions/vehicle-protected-question/vehicle-protected-question";
@ -96,13 +90,14 @@ export default {
"updated-mobile-fee-part",
"updated-contains-military-base",
"updated-bill-to-account-number",
"setMobileLocationAndProceed",
"setMobileLocation",
"set-mobile-location-invalid",
],
data() {
return {
internalModel: deepClone(this.modelValue),
displayInvalidZipAlert: false,
isModalOpened: false,
isMobileLocationOpened: false,
displayMismatchStateAndZipAlert: false,
};
},
@ -188,23 +183,29 @@ export default {
this.mobileFeePart.kitPrice
);
},
modalName() {
return "MobileLocationModalWidget";
},
modalHeaderText() {
headerText() {
return this.getCmsContent(this.modalWidgetName, "HeaderText");
},
modalFooterText() {
return this.getCmsContent(this.modalWidgetName, "FooterText");
},
modal() {
return this.$refs[this.modalName];
},
addressModel: {
get: function () {
return this.modelValue.addressQuestions;
},
},
isMobileAddressAvailable: {
get: function () {
return (
this.addressModel.streetAddress &&
this.addressModel.streetAddress !== "" &&
this.addressModel.city &&
this.addressModel.city !== "" &&
this.addressModel.state &&
this.addressModel.state !== "" &&
this.addressModel.zipCode &&
this.addressModel.zipCode !== "" &&
this.internalModel.isVehicleProtected !== null
);
},
},
},
methods: {
isMobileAddressComplete() {
@ -220,24 +221,14 @@ export default {
this.internalModel.isVehicleProtected !== null
);
},
openModal() {
this.modal.openModal();
getIsMobleLocationOpened() {
if (this.isServiceAddressFromStoreAvailable) {
return false;
}
return true;
},
onModalOpened() {
this.internalModel = deepClone(this.modelValue);
},
setModalStatus(isOpened) {
this.isModalOpened = isOpened;
},
closeModal() {
this.modal.closeModal();
},
onModalClosed() {
this.resetAlerts();
this.internalModel = deepClone(this.modelValue);
},
resetModalButtonStyle() {
this.modal.resetButtonStyle();
setMobileLocationInvalid(isFormInvalid) {
this.$emit("set-mobile-location-invalid", isFormInvalid);
},
resetAlerts() {
this.displayInvalidZipAlert = false;
@ -246,6 +237,10 @@ export default {
async setMobileLocation() {
this.resetAlerts();
// Validate the Zip Code
if (this.internalModel.addressQuestions.zipCode === "") {
this.setMobileLocationInvalid(true);
return;
}
const zipCodeData = await this.getZipCodeData(
this.internalModel.addressQuestions.zipCode,
"service-location"
@ -253,11 +248,11 @@ export default {
if (!zipCodeData.isValid) {
this.displayInvalidZipAlert = true;
this.resetModalButtonStyle();
this.setMobileLocationInvalid(true);
return;
} else if (zipCodeData.state != this.internalModel.addressQuestions.state) {
this.displayMismatchStateAndZipAlert = true;
this.resetModalButtonStyle();
this.setMobileLocationInvalid(true);
return;
} else if (
this.internalModel.addressQuestions.zipCode !==
@ -296,10 +291,21 @@ export default {
this.$emit("updated-bill-to-account-number", billToAccountNumber);
}
// update the page level model
this.$emit("setMobileLocationAndProceed", this.internalModel);
this.$emit("setMobileLocation", this.internalModel);
this.setMobileLocationInvalid(false);
},
toggleMobileLocation() {
this.isMobileLocationOpened = !this.isMobileLocationOpened;
},
},
watch: {
internalModel: {
handler() {
this.setMobileLocation();
},
deep: true,
},
modelValue: {
handler(newValue) {
this.internalModel = deepClone(newValue);
@ -308,15 +314,11 @@ export default {
deep: true,
},
},
unmounted() {
if (this.isModalOpened) this.closeModal();
},
components: {
textLink,
modal,
addressQuestions,
vehicleProtectedQuestion,
textBlock,
textLink,
alert,
},
};
@ -327,28 +329,34 @@ export default {
.center-error-message {
justify-content: center !important;
}
}
.update-zip-text-link::before {
content: "";
display: inline-block;
width: 13px;
height: 16px;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 13 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.49635 1.00142e-07C5.64734 -0.000153295 4.80722 0.175918 4.0274 0.517444C3.24757 0.858969 2.54443 1.35877 1.96099 1.98626C0.765713 3.27588 0.0999756 4.98141 0.0999756 6.75394C0.0999756 8.52646 0.765713 10.232 1.96099 11.5216L5.98324 15.777C6.04954 15.8475 6.12918 15.9036 6.21736 15.9419C6.30555 15.9802 6.40045 16 6.49635 16C6.59225 16 6.68716 15.9802 6.77534 15.9419C6.86353 15.9036 6.94317 15.8475 7.00946 15.777L11.0317 11.52C12.2391 10.2383 12.909 8.52914 12.8999 6.75394C12.9094 4.97818 12.2394 3.26832 11.0317 1.98626C10.4481 1.35899 9.74493 0.859347 8.96514 0.517839C8.18535 0.176331 7.34532 0.000130509 6.49635 1.00142e-07V1.00142e-07ZM6.49635 9.13131C6.02507 9.13131 5.56437 8.98913 5.17251 8.72275C4.78065 8.45637 4.47524 8.07776 4.29488 7.63479C4.11453 7.19181 4.06734 6.70438 4.15928 6.23412C4.25123 5.76387 4.47817 5.33191 4.81142 4.99287C5.14467 4.65384 5.56925 4.42295 6.03148 4.32941C6.49371 4.23587 6.97282 4.28388 7.40823 4.46736C7.84364 4.65085 8.21579 4.96157 8.47762 5.36023C8.73945 5.7589 8.87921 6.2276 8.87921 6.70707C8.87921 7.34974 8.62837 7.96611 8.18185 8.4207C7.73532 8.87528 7.12964 9.13088 6.49794 9.13131H6.49635Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
background-size: contain;
vertical-align: middle;
margin-right: 0.5em;
}
.update-zip-text-link::before {
content: "";
display: inline-block;
width: 13px;
height: 16px;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 13 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.49635 1.00142e-07C5.64734 -0.000153295 4.80722 0.175918 4.0274 0.517444C3.24757 0.858969 2.54443 1.35877 1.96099 1.98626C0.765713 3.27588 0.0999756 4.98141 0.0999756 6.75394C0.0999756 8.52646 0.765713 10.232 1.96099 11.5216L5.98324 15.777C6.04954 15.8475 6.12918 15.9036 6.21736 15.9419C6.30555 15.9802 6.40045 16 6.49635 16C6.59225 16 6.68716 15.9802 6.77534 15.9419C6.86353 15.9036 6.94317 15.8475 7.00946 15.777L11.0317 11.52C12.2391 10.2383 12.909 8.52914 12.8999 6.75394C12.9094 4.97818 12.2394 3.26832 11.0317 1.98626C10.4481 1.35899 9.74493 0.859347 8.96514 0.517839C8.18535 0.176331 7.34532 0.000130509 6.49635 1.00142e-07V1.00142e-07ZM6.49635 9.13131C6.02507 9.13131 5.56437 8.98913 5.17251 8.72275C4.78065 8.45637 4.47524 8.07776 4.29488 7.63479C4.11453 7.19181 4.06734 6.70438 4.15928 6.23412C4.25123 5.76387 4.47817 5.33191 4.81142 4.99287C5.14467 4.65384 5.56925 4.42295 6.03148 4.32941C6.49371 4.23587 6.97282 4.28388 7.40823 4.46736C7.84364 4.65085 8.21579 4.96157 8.47762 5.36023C8.73945 5.7589 8.87921 6.2276 8.87921 6.70707C8.87921 7.34974 8.62837 7.96611 8.18185 8.4207C7.73532 8.87528 7.12964 9.13088 6.49794 9.13131H6.49635Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
background-size: contain;
vertical-align: middle;
margin-right: 0.5em;
}
.address-questions {
margin-top: 0em;
}
.update-mobile-location-text-link {
white-space: pre-line;
}
.address-questions-container {
margin-top: 1rem;
}
.update-mobile-location-text-link {
white-space: pre-line;
}
.text-black {
color: $black;
.text-black {
color: $black;
}
.HeaderText {
font-size: 1rem;
font-weight: 600;
line-height: 1.625rem;
color: $black;
text-align: center;
}
}
</style>

View file

@ -7,7 +7,8 @@
textPosition="text-center"
v-model="selectedValue"
validationRules="option-required"
isRequired />
isRequired
:labelBold="labelBold" />
</template>
<script>
@ -27,6 +28,11 @@ export default {
isVehicleProtected: Boolean,
},
cmsWidgetName: String,
labelBold: {
type: Boolean,
required: false,
default: false,
},
},
components: {
buttonQuestion,

View file

@ -343,7 +343,6 @@ describe("service-location.vue", () => {
zipCode: "43054",
},
isVehicleProtected: true,
isMobileSelected: true,
};
wrapper.vm.mobileLocationQuestions = mobileLocationQuestions;
@ -356,7 +355,6 @@ describe("service-location.vue", () => {
zipCode: "61606",
},
isVehicleProtected: null,
isMobileSelected: false,
};
// Act
@ -459,7 +457,6 @@ describe("service-location.vue", () => {
zipCode: "",
},
isVehicleProtected: null,
isMobileSelected: false,
};
wrapper.vm.mobileLocationQuestions = mobileLocationQuestions;
const newMobileLocationQuestions = {
@ -471,15 +468,14 @@ describe("service-location.vue", () => {
zipCode: "43054",
},
isVehicleProtected: true,
isMobileSelected: true,
};
wrapper.vm.closeModalAction = jest.fn();
//wrapper.vm.closeModalAction = jest.fn();
// Act
// Trigger the event
await mobileLocationQuestionsComponent.vm.$emit(
"setMobileLocationAndProceed",
"setMobileLocation",
newMobileLocationQuestions
);
@ -521,7 +517,7 @@ describe("service-location.vue", () => {
zipCode: "",
},
isVehicleProtected: null,
isMobileSelected: false,
isMobileSelectedAndModalClosed: false,
mobileFeePart: null,
};
wrapper.vm.mobileLocationQuestions = mobileLocationQuestions;
@ -535,7 +531,7 @@ describe("service-location.vue", () => {
zipCode: "43081",
},
isVehicleProtected: true,
isMobileSelected: true,
isMobileSelectedAndModalClosed: true,
mobileFeePart: null,
};
@ -556,7 +552,7 @@ describe("service-location.vue", () => {
// Trigger the event
await mobileLocationQuestionsComponent.vm.$emit(
"setMobileLocationAndProceed",
"setMobileLocation",
newMobileLocationQuestions
);
@ -599,15 +595,12 @@ describe("service-location.vue", () => {
zipCode: "43081",
},
isVehicleProtected: "YesAnswer",
isMobileSelected: true,
isMobileSelectedAndModalClosed: true,
};
wrapper.vm.closeModalAction = jest.fn();
// Act
mobileLocationQuestionsComponent.vm.$emit(
"setMobileLocationAndProceed",
mobileLocationQuestions
);
mobileLocationQuestionsComponent.vm.$emit("setMobileLocation", mobileLocationQuestions);
// Assert
expect(wrapper.vm.selectedAppointmentType).toStrictEqual("Mobile");

View file

@ -79,10 +79,12 @@
:isServiceableDropoff="isServiceableDropoff"
:isDisplayed="isAppointmentTypeDisplayed"
:mobileFeeApplies="mobileFeeApplies"
:zipCode="zipCode"
ref="appointmentTypeQuestion"
groupName="appointmentTypeQuestion"
cmsWidgetName="AppointmentTypeQuestionWidget"
validationRules="option-required" />
validationRules="option-required"
labelBold="true" />
</div>
</div>
<div class="row justify-content-center">
@ -102,7 +104,8 @@
ref="mobileLocationQuestions"
linkWidgetName="MobileLocationLinkWidget"
modalWidgetName="MobileLocationModalWidget"
@setMobileLocationAndProceed="setMobileLocationQuestions" />
@setMobileLocation="setMobileLocationQuestions"
@set-mobile-location-invalid="setMobileLocationInValid" />
<shopQuestion
ref="shopQuestion"
@ -111,15 +114,14 @@
:selectedAppointmentType="selectedAppointmentType"
:shopProviderData="shopProviderData"
:isDisplayed="isShopQuestionDisplayed"
cmsWidgetName="ShopQuestionWidget"
:isInshopOnly="isInshopOnly" />
cmsWidgetName="ShopQuestionWidget" />
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
<navbar
cmsWidgetName="FunnelFooterWidget"
ref="navbar"
:isForwardActionDisabled="!meta.valid || displayNoShopsAlert"
:isForwardActionDisabled="!meta.valid || isForwardActionDisabled"
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction" />
</div>
@ -173,12 +175,12 @@ const MOBILE_FEE_PART_TYPE = "MOBILE FEE";
// DEFINE VALIDATION RULES
defineRule("mobile-location-required", (value) => {
if (
value.isMobileSelected &&
(!value.addressQuestions.streetAddress ||
!value.addressQuestions.city ||
!value.addressQuestions.state ||
!value.addressQuestions.zipCode ||
!value.isVehicleProtected)
!value.isVehicleProtected) &&
value.isMobileSelected
) {
return errorMessages.MOBILE_LOCATION_REQUIRED;
}
@ -212,6 +214,7 @@ export default {
billToAccountNumber: null,
shopProviderData: null,
navigatingForward: false,
isMobileAddressValid: true,
};
},
async beforeRouteEnter(to, from, next) {
@ -302,7 +305,6 @@ export default {
zipCode: this.zipCode,
},
isVehicleProtected: this.isVehicleProtected,
isMobileSelected: this.selectedAppointmentType === "Mobile",
};
},
},
@ -416,10 +418,8 @@ export default {
isNoComp() {
return store.getters.order.policy.isNoComp;
},
isInshopOnly() {
return (
this.isServiceableInshop && !this.isServiceableMobile && !this.isServiceableDropoff
);
isForwardActionDisabled() {
return this.displayNoShopsAlert || !this.isMobileAddressValid;
},
},
methods: {
@ -523,10 +523,7 @@ export default {
}
if (this.isServiceableMobile) {
this.setMobileLocation(newValue);
//proceed to next page
this.forwardButtonAction();
} else {
this.closeModalAction("mobileLocationQuestions");
await getZipCodeData(newZipCode).then((zipCodeData) => {
this.serviceZipCodeQuestion = {
state: zipCodeData.state,
@ -694,7 +691,7 @@ export default {
},
async forwardButtonAction() {
this.navigatingForward = true;
var ctu = this.zipCodeCtu;
if (
this.selectedAppointmentType == AppointmentTypeStrings.MOBILE &&
this.selectedProvider &&
@ -705,18 +702,13 @@ export default {
this.selectedProvider.address.state = null;
this.selectedProvider.address.zipCode = null;
this.selectedProvider.address.zipCodeCtu = null;
}
var ctu = this.zipCodeCtu;
if (this.selectedAppointmentType == AppointmentTypeStrings.IN_SHOP) {
} else {
// if we have a provider.zipCodeCtu that's different than the servicelocation.zipCodeCtu then they
// may have selected a shop in a different ctu. change the servicelocation zip/ctu if different
if (this.zipCodeCtu != this.selectedProvider.address.zipCodeCtu) {
ctu = this.selectedProvider.address.zipCodeCtu;
}
this.city = null;
this.streetAddress = null;
this.resetMobileLocation();
}
await this.dispatchStoreAction(
@ -756,11 +748,15 @@ export default {
this.updateAndSaveIsMSRFeeApplicable();
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
},
openModalAction(modalName) {
this.$refs[modalName].openModal();
setMobileLocationInValid(isMobileLocationInValid) {
this.isMobileAddressValid = !isMobileLocationInValid;
},
closeModalAction(modalName) {
this.$refs[modalName].closeModal();
displayMobileAddressQuestion(openMobileLocation) {
var mobileLocationQuestionsRef = this.$refs.mobileLocationQuestions;
var isMobileAddressComplete = mobileLocationQuestionsRef.isMobileAddressComplete;
if (isMobileAddressComplete) {
mobileLocationQuestionsRef.isMobileLocationOpened = openMobileLocation;
}
},
},
watch: {
@ -773,8 +769,10 @@ export default {
this.selectedProvider = new Provider(
this.shopProviderData.mobileProviderNumber
);
this.displayMobileAddressQuestion(true);
} else {
this.selectedProvider = new Provider();
this.displayMobileAddressQuestion(false);
}
});
}
@ -786,12 +784,10 @@ export default {
this.selectedProvider = new Provider(
this.shopProviderData.mobileProviderNumber
);
var mobileLocationQs = this.$refs.mobileLocationQuestions;
if (!mobileLocationQs.isMobileAddressComplete()) {
mobileLocationQs.openModal();
}
this.displayMobileAddressQuestion(true);
} else {
this.selectedProvider = new Provider();
this.displayMobileAddressQuestion(false);
}
},
},

View file

@ -77,7 +77,6 @@ export default {
cmsWidgetName: String,
validationRules: String,
isDisplayed: Boolean,
isInshopOnly: Boolean,
},
computed: {
questionText() {
@ -234,13 +233,9 @@ export default {
// nothing to do with this component. We could mitigate this by showing / hiding this component with v-if but that messes
// up the component initialization on page load.
async handler() {
//We do not need to run this handler in case of Inshop only else it will ended running twice function getNextShopsFromList and will cause pagination
if (!this.isInshopOnly) {
this.resetAnswers();
await nextTick();
this.getNextShopsFromList();
}
await nextTick();
this.resetAnswers();
this.getNextShopsFromList();
},
},
shopProviders: {

View file

@ -30,6 +30,7 @@ import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import router from "@/router/index.js";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { Variables } from "../constants/analytics";
export default {
methods: {
@ -120,6 +121,10 @@ export default {
}
},
async pushVariableToDataLayer(data) {
pushToDataLayerIfDefined(data);
},
async pushPageViewToGA() {
const currentPageName = getPageNameByQueryString();
const pageViewEvent = {
@ -306,6 +311,18 @@ export default {
} else {
payload.priceSubTotal = "";
}
// Cash Quote or Cash Price Sub Total
if (isPricingAvailable) {
const subtotal = baseMixin.methods
.getTotalPriceOfAllLineItemsAndChildParts(combinedLineItems, false)
.toFixed(2);
payload.cashPriceSubTotal = parseFloat(subtotal);
} else {
payload.cashPriceSubTotal = "";
}
//unverified (in scenarios we dont display the price)
if (
order.payment.isInsurance &&
@ -770,6 +787,9 @@ export default {
ValueToLogTypes() {
return ValueToLogTypes;
},
Variables() {
return Variables;
},
},
};

View file

@ -340,6 +340,10 @@ export const mutations = {
updateIsMSRFeeApplicable(state, isMSRFeeApplicable) {
state.order.isMSRFeeApplicable = isMSRFeeApplicable;
},
updateCashPriceSubTotal(state, cashPriceSubTotal) {
state.order.cashPriceSubTotal =
cashPriceSubTotal === "" ? null : cashPriceSubTotal.toString();
},
updateCCToken(state, ccToken) {
state.order.payment.ccToken.subscriptionId = ccToken.subscriptionId;
state.order.payment.ccToken.expMonth = ccToken.expMonth;
@ -1732,7 +1736,10 @@ export const actions = {
return response;
},
getMobileFeePart(context, { payload: { serviceZipCode, serviceZipCodeCtu }, pageNameToLog }) {
getMobileFeePart(
context,
{ payload: { serviceZipCode, serviceZipCodeCtu, mobileProviderNumber }, pageNameToLog }
) {
const serviceType = context.getters.damage.isRepair ? "Repair" : "Install";
const facilityType = "Mobile";
const parentAccountNumber = context.getters.payment.parentAccountNumber;
@ -1750,6 +1757,7 @@ export const actions = {
order.serviceLocation?.zipCode;
var providerNumber =
mobileProviderNumber ??
serviceZipCodeCtu ??
order.serviceLocation?.provider?.providerNumber ??
order.serviceLocation?.zipCodeCtu;
@ -2339,6 +2347,7 @@ export const actions = {
lockToken: order.lockToken,
isRecalAckOptIn: order.isRecalAckOptIn,
isMSRFeeApplicable: order.isMSRFeeApplicable,
cashPriceSubTotal: order.cashPriceSubTotal,
},
},
logApiCall: true,

View file

@ -6,7 +6,7 @@
<div
:aria-label="buttonLabel"
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4 p-md-4">
<span class="m-0" :class="textPosition">
<span class="m-0" :class="[textPosition, labelBold ? 'span-bold' : '']">
{{ buttonLabel }}
</span>
<span v-if="buttonLabelSubCopy" class="m-0 small" :class="textPosition">
@ -88,6 +88,9 @@ export default {
&:checked + .list-button-content span {
font-weight: 500;
}
&:checked + .list-button-content .span-bold {
font-weight: 600;
}
&:checked + .list-button-content span:nth-child(2) {
font-weight: 400;
color: $gray-600;

View file

@ -15,7 +15,10 @@
:class="!isWide ? 'order-1' : 'ms-auto order-3'"
:src="buttonImage"
:alt="altText" />
<p v-if="!isWide" class="small order-3" :class="isMultiSelect ? 'm-0' : 'mt-2 mb-0'">
<p
v-if="!isWide"
class="small order-3"
:class="[isMultiSelect ? 'm-0' : 'mt-2 mb-0', labelBold ? 'p-bold' : '']">
{{ buttonLabel }}
</p>
<p v-if="buttonLabelSubCopy && !isWide" class="fs-7 m-0 order-4 sub-copy">
@ -122,6 +125,9 @@ export default {
color: $black;
font-weight: 500;
}
.p-bold {
font-weight: 600;
}
.sub-copy {
color: $gray-600;