Merge branch 'develop' into CASH-1042-header-and-progress-bar-style-updates
This commit is contained in:
commit
3d9ca2f44d
60 changed files with 1327 additions and 1521 deletions
|
|
@ -31,6 +31,8 @@ module.exports = {
|
|||
"!src/layouts/payment-pia-return/*.vue", // Temp test exclusion while in development
|
||||
"!src/layouts/insurance/*.vue", // Temp test exclusion while in development
|
||||
"!src/layouts/insurance-company/*.vue", // Temp test exclusion while in development
|
||||
"!src/layouts/schedule/**/*.vue", // Temp test exclusion while in development
|
||||
"!src/digital-components/date-picker/**/*.vue", // Temp test exclusion while in development
|
||||
|
||||
"!src/**/*-june-2025.vue", // Exclude these temporary files for CASH-845 project
|
||||
|
||||
|
|
@ -41,7 +43,7 @@ module.exports = {
|
|||
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
statements: 68,
|
||||
statements: 66,
|
||||
},
|
||||
},
|
||||
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit
|
||||
|
|
|
|||
|
|
@ -194,6 +194,16 @@ html .in-shop.vspacing .list-group.radio.vspacing .list-group-item {
|
|||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
@media (min-width: 500px) {
|
||||
.form-group {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
@media (min-width: 800px) {
|
||||
.form-group {
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
.list-group {
|
||||
margin-bottom: 20px;
|
||||
padding-left: 0;
|
||||
|
|
|
|||
|
|
@ -188,6 +188,17 @@
|
|||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
@media (min-width: 500px) {
|
||||
.form-group {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
@media (min-width: 800px) {
|
||||
.form-group {
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
.list-group {
|
||||
margin-bottom: 20px;
|
||||
padding-left: 0;
|
||||
|
|
|
|||
3
src/assets/img/icons/question-mark-icon-blue.svg
Normal file
3
src/assets/img/icons/question-mark-icon-blue.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 6.3201e-10C6.01675 -2.49147e-05 4.1042 0.736615 2.63337 2.06701C1.16255 3.3974 0.238325 5.22669 0.04 7.2C0.0144 7.4624 0 7.7296 0 8C0 8.2704 0.0144 8.5376 0.04 8.8C0.194814 10.3404 0.793246 11.8027 1.76272 13.0097C2.73219 14.2167 4.03105 15.1165 5.50178 15.6C6.9725 16.0834 8.5519 16.1297 10.0484 15.7333C11.5449 15.3368 12.8943 14.5147 13.9328 13.3666C14.9714 12.2185 15.6544 10.7937 15.8992 9.26502C16.1441 7.73636 15.9401 6.16951 15.312 4.75449C14.6839 3.33948 13.6588 2.13709 12.3609 1.29316C11.063 0.449234 9.54814 1.94497e-05 8 6.32009e-10V6.3201e-10ZM8.3312 11.68C8.27055 11.737 8.19865 11.7807 8.12012 11.8082C8.04159 11.8358 7.95816 11.8466 7.8752 11.84C7.79544 11.8483 7.71483 11.8391 7.63899 11.8131C7.56316 11.787 7.49394 11.7446 7.43618 11.689C7.37842 11.6334 7.33351 11.5658 7.3046 11.491C7.27569 11.4162 7.26347 11.336 7.2688 11.256C7.26648 11.0995 7.32367 10.9479 7.4288 10.832C7.48751 10.7721 7.55859 10.7257 7.63708 10.6962C7.71556 10.6666 7.79956 10.6545 7.8832 10.6608C7.96706 10.6547 8.05124 10.6669 8.12996 10.6964C8.20867 10.726 8.28006 10.7722 8.3392 10.832C8.44547 10.9473 8.50283 11.0993 8.4992 11.256C8.50107 11.3343 8.48716 11.4122 8.4583 11.4851C8.42944 11.5579 8.38621 11.6242 8.3312 11.68ZM9.3552 8.1744L8.7488 8.7744C8.54248 9.01481 8.41218 9.31105 8.3744 9.6256C8.35965 9.74981 8.30021 9.86441 8.20718 9.94802C8.11415 10.0316 7.99387 10.0785 7.8688 10.08C7.79446 10.0802 7.72097 10.0643 7.65339 10.0333C7.58581 10.0024 7.52575 9.95709 7.47738 9.90065C7.429 9.8442 7.39345 9.77794 7.37318 9.70641C7.35292 9.63489 7.34842 9.55983 7.36 9.4864C7.38057 9.33018 7.41648 9.17637 7.4672 9.0272C7.60032 8.7102 7.79315 8.42177 8.0352 8.1776L8.7648 7.4272C9.05746 7.1218 9.22428 6.71732 9.232 6.2944C9.25213 5.96201 9.14193 5.63487 8.9248 5.3824C8.80862 5.26708 8.66916 5.17788 8.51575 5.12076C8.36234 5.06365 8.19851 5.03993 8.0352 5.0512C7.6576 5.0512 7.3392 5.136 7.1232 5.352C6.9072 5.568 6.8144 6.0128 6.7904 6.3744C6.78584 6.44174 6.76797 6.5075 6.73783 6.56789C6.70768 6.62828 6.66586 6.68208 6.61478 6.7262C6.5637 6.77032 6.50438 6.80386 6.44025 6.8249C6.37612 6.84593 6.30845 6.85404 6.24116 6.84875C6.17388 6.84346 6.10831 6.82488 6.04825 6.79408C5.9882 6.76328 5.93485 6.72088 5.89129 6.66932C5.84773 6.61777 5.81483 6.55808 5.79449 6.49373C5.77415 6.42937 5.76678 6.36162 5.7728 6.2944C5.8176 5.7584 5.9552 5.1408 6.392 4.704C6.7872 4.3088 7.352 4.1504 8.0352 4.1504C8.736 4.1504 9.28213 4.33867 9.6736 4.7152C10.0656 5.0896 10.2608 5.6064 10.2608 6.2608C10.2608 6.9152 9.9552 7.5472 9.3552 8.1776V8.1744Z" fill="#0070D1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
|
|
@ -7,7 +7,7 @@ https://safelite.atlassian.net/wiki/spaces/DC/pages/76644418/Button+Question+Com
|
|||
">
|
||||
<div
|
||||
v-if="questionText && answers && answers.length > 0"
|
||||
class="question-text"
|
||||
class="question-text d-flex"
|
||||
:class="{ 'small-question-text': isSmallQuestionText }">
|
||||
<span class="fw-bold w-100" :class="[labelBold ? 'span-bold' : '']">
|
||||
{{ questionText }}
|
||||
|
|
@ -83,7 +83,10 @@ https://safelite.atlassian.net/wiki/spaces/DC/pages/76644418/Button+Question+Com
|
|||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div id="form-test-error" class="row form-test-error">
|
||||
<div
|
||||
id="form-test-error"
|
||||
class="row form-test-error"
|
||||
:class="isErrorCentered ? 'text-center' : ''">
|
||||
<error-message
|
||||
role="comment"
|
||||
aria-atomic="true"
|
||||
|
|
@ -258,6 +261,12 @@ export default {
|
|||
|
||||
return classes;
|
||||
},
|
||||
isErrorCentered() {
|
||||
if (this.buttonTypeString == "listCard" && this.buttonsInfo.length <= 2) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
buttonsInfo() {
|
||||
return (Array.isArray(this.answers) ? this.answers : [])?.map((answer) => ({
|
||||
buttonLabel: answer.buttonLabel ?? answer.Text ?? answer,
|
||||
|
|
|
|||
|
|
@ -674,140 +674,140 @@ describe("date-picker.vue", () => {
|
|||
wrapper.unmount();
|
||||
});
|
||||
|
||||
describe("when loadInitialData is run...", () => {
|
||||
test("and this.todayString exists, expect it should have a todayDate matching today", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
const todayDate = new Date();
|
||||
const todayDateString =
|
||||
todayDate.getFullYear() +
|
||||
"-" +
|
||||
("0" + (todayDate.getMonth() + 1)).slice(-2) +
|
||||
"-" +
|
||||
("0" + todayDate.getDate()).slice(-2);
|
||||
// describe("when loadInitialData is run...", () => {
|
||||
// test("and this.todayString exists, expect it should have a todayDate matching today", async () => {
|
||||
// // Arrange
|
||||
// const { wrapper } = setupMocks({});
|
||||
// const todayDate = new Date();
|
||||
// const todayDateString =
|
||||
// todayDate.getFullYear() +
|
||||
// "-" +
|
||||
// ("0" + (todayDate.getMonth() + 1)).slice(-2) +
|
||||
// "-" +
|
||||
// ("0" + todayDate.getDate()).slice(-2);
|
||||
|
||||
// Act
|
||||
const testResult = await wrapper.vm.loadInitialData({
|
||||
selectableDatesSetting: "custom",
|
||||
initialViewRowsToShow: 5,
|
||||
getSelectableDatesCallback: mockPromiseResolve,
|
||||
preSelectedDate: null,
|
||||
});
|
||||
// // Act
|
||||
// const testResult = await wrapper.vm.loadInitialData({
|
||||
// selectableDatesSetting: "custom",
|
||||
// initialViewRowsToShow: 5,
|
||||
// getSelectableDatesCallback: mockPromiseResolve,
|
||||
// preSelectedDate: null,
|
||||
// });
|
||||
|
||||
// Assert
|
||||
expect(testResult).toMatchObject({
|
||||
todayDate: todayDateString,
|
||||
});
|
||||
// // Assert
|
||||
// expect(testResult).toMatchObject({
|
||||
// todayDate: todayDateString,
|
||||
// });
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
// wrapper.unmount();
|
||||
// });
|
||||
|
||||
test("and overrride date exists, expect it should have a todayDate matching the override", async () => {
|
||||
// Arrange
|
||||
const overrideDate = mockValuesForSetDate01.overrideDate;
|
||||
const { wrapper } = setupMocks({
|
||||
propsData: {
|
||||
selectableDatesSetting: "custom",
|
||||
todayOverrideDateString: overrideDate,
|
||||
},
|
||||
});
|
||||
// test("and overrride date exists, expect it should have a todayDate matching the override", async () => {
|
||||
// // Arrange
|
||||
// const overrideDate = mockValuesForSetDate01.overrideDate;
|
||||
// const { wrapper } = setupMocks({
|
||||
// propsData: {
|
||||
// selectableDatesSetting: "custom",
|
||||
// todayOverrideDateString: overrideDate,
|
||||
// },
|
||||
// });
|
||||
|
||||
// Act
|
||||
const localThis = {
|
||||
todayString: null,
|
||||
todayOverrideDateString: overrideDate,
|
||||
getMonthEnd: () => mockValuesForSetDate01.monthEnd,
|
||||
getInitialViewWeeks: () => mockValuesForSetDate01.initialViewWeeks,
|
||||
};
|
||||
const testResult = await datePicker.methods.loadInitialData.call(localThis, {
|
||||
selectableDatesSetting: "custom",
|
||||
initialViewRowsToShow: 5,
|
||||
getSelectableDatesCallback: mockPromiseResolve,
|
||||
preSelectedDate: null,
|
||||
// // Act
|
||||
// const localThis = {
|
||||
// todayString: null,
|
||||
// todayOverrideDateString: overrideDate,
|
||||
// getMonthEnd: () => mockValuesForSetDate01.monthEnd,
|
||||
// getInitialViewWeeks: () => mockValuesForSetDate01.initialViewWeeks,
|
||||
// };
|
||||
// const testResult = await datePicker.methods.loadInitialData.call(localThis, {
|
||||
// selectableDatesSetting: "custom",
|
||||
// initialViewRowsToShow: 5,
|
||||
// getSelectableDatesCallback: mockPromiseResolve,
|
||||
// preSelectedDate: null,
|
||||
|
||||
todayOverrideDateString: overrideDate,
|
||||
});
|
||||
// todayOverrideDateString: overrideDate,
|
||||
// });
|
||||
|
||||
// Assert
|
||||
expect(testResult).toMatchObject({
|
||||
todayDate: overrideDate,
|
||||
});
|
||||
// // Assert
|
||||
// expect(testResult).toMatchObject({
|
||||
// todayDate: overrideDate,
|
||||
// });
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
// wrapper.unmount();
|
||||
// });
|
||||
|
||||
test("with no this.todayString or overrride date, expect it should have a todayDate matching today", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
const todayDate = new Date();
|
||||
const todayDateString =
|
||||
todayDate.getFullYear() +
|
||||
"-" +
|
||||
("0" + (todayDate.getMonth() + 1)).slice(-2) +
|
||||
"-" +
|
||||
("0" + todayDate.getDate()).slice(-2);
|
||||
// test("with no this.todayString or overrride date, expect it should have a todayDate matching today", async () => {
|
||||
// // Arrange
|
||||
// const { wrapper } = setupMocks({});
|
||||
// const todayDate = new Date();
|
||||
// const todayDateString =
|
||||
// todayDate.getFullYear() +
|
||||
// "-" +
|
||||
// ("0" + (todayDate.getMonth() + 1)).slice(-2) +
|
||||
// "-" +
|
||||
// ("0" + todayDate.getDate()).slice(-2);
|
||||
|
||||
// Act
|
||||
const localThis = {
|
||||
todayString: null,
|
||||
getMonthEnd: () => mockValuesForSetDate01.monthEnd,
|
||||
getInitialViewWeeks: () => mockValuesForSetDate01.initialViewWeeks,
|
||||
};
|
||||
// // Act
|
||||
// const localThis = {
|
||||
// todayString: null,
|
||||
// getMonthEnd: () => mockValuesForSetDate01.monthEnd,
|
||||
// getInitialViewWeeks: () => mockValuesForSetDate01.initialViewWeeks,
|
||||
// };
|
||||
|
||||
//Act
|
||||
const testResult = await datePicker.methods.loadInitialData.call(localThis, {
|
||||
selectableDatesSetting: "custom",
|
||||
initialViewRowsToShow: 5,
|
||||
getSelectableDatesCallback: mockPromiseResolve,
|
||||
preSelectedDate: null,
|
||||
});
|
||||
// //Act
|
||||
// const testResult = await datePicker.methods.loadInitialData.call(localThis, {
|
||||
// selectableDatesSetting: "custom",
|
||||
// initialViewRowsToShow: 5,
|
||||
// getSelectableDatesCallback: mockPromiseResolve,
|
||||
// preSelectedDate: null,
|
||||
// });
|
||||
|
||||
// Assert
|
||||
expect(testResult).toMatchObject({
|
||||
todayDate: todayDateString,
|
||||
});
|
||||
// // Assert
|
||||
// expect(testResult).toMatchObject({
|
||||
// todayDate: todayDateString,
|
||||
// });
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
// wrapper.unmount();
|
||||
// });
|
||||
|
||||
test("and config.selectableDatesSetting is PAST, expect it should have a calendarViewDirection of PAST", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
// test("and config.selectableDatesSetting is PAST, expect it should have a calendarViewDirection of PAST", async () => {
|
||||
// // Arrange
|
||||
// const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const testResult = await wrapper.vm.loadInitialData({
|
||||
selectableDatesSetting: "past",
|
||||
initialViewRowsToShow: 5,
|
||||
getSelectableDatesCallback: mockPromiseResolve,
|
||||
});
|
||||
// // Act
|
||||
// const testResult = await wrapper.vm.loadInitialData({
|
||||
// selectableDatesSetting: "past",
|
||||
// initialViewRowsToShow: 5,
|
||||
// getSelectableDatesCallback: mockPromiseResolve,
|
||||
// });
|
||||
|
||||
// Assert
|
||||
expect(testResult).toMatchObject({
|
||||
calendarViewDirection: "past",
|
||||
});
|
||||
// // Assert
|
||||
// expect(testResult).toMatchObject({
|
||||
// calendarViewDirection: "past",
|
||||
// });
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
// wrapper.unmount();
|
||||
// });
|
||||
|
||||
test("and config.selectableDatesSetting is CUSTOM, expect it should have a calendarViewDirection of FUTURE", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
// test("and config.selectableDatesSetting is CUSTOM, expect it should have a calendarViewDirection of FUTURE", async () => {
|
||||
// // Arrange
|
||||
// const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const testResult = await wrapper.vm.loadInitialData({
|
||||
selectableDatesSetting: "custom",
|
||||
initialViewRowsToShow: 5,
|
||||
getSelectableDatesCallback: mockPromiseResolve,
|
||||
});
|
||||
// // Act
|
||||
// const testResult = await wrapper.vm.loadInitialData({
|
||||
// selectableDatesSetting: "custom",
|
||||
// initialViewRowsToShow: 5,
|
||||
// getSelectableDatesCallback: mockPromiseResolve,
|
||||
// });
|
||||
|
||||
// Assert
|
||||
expect(testResult).toMatchObject({
|
||||
calendarViewDirection: "future",
|
||||
});
|
||||
// // Assert
|
||||
// expect(testResult).toMatchObject({
|
||||
// calendarViewDirection: "future",
|
||||
// });
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
// wrapper.unmount();
|
||||
// });
|
||||
// });
|
||||
|
||||
describe("when setCalendarData is run...", () => {
|
||||
test("expect that data elements are updated properly", async () => {
|
||||
|
|
@ -307,6 +307,7 @@ export default {
|
|||
},
|
||||
set(newSelectedDate) {
|
||||
this.$emit("update:modelValue", newSelectedDate);
|
||||
this.dispatchStoreAction(this.storeActions.SAVE_WAITLIST_REQUESTED, false, false);
|
||||
},
|
||||
},
|
||||
dropOffDurationText() {
|
||||
|
|
@ -335,11 +336,32 @@ export default {
|
|||
this.estimatedServiceMinutesMaximum
|
||||
);
|
||||
|
||||
return `${inshopDurationTextWithoutTime} ${inshopDurationTime}`;
|
||||
if (this.estimatedServiceMinutesMinimum && this.estimatedServiceMinutesMaximum) {
|
||||
return `${inshopDurationTextWithoutTime} ${inshopDurationTime}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
mobileDurationText() {
|
||||
const mobileDurationTextWithoutTime = this.getCmsContent(
|
||||
"TimeSlotModalQuestion",
|
||||
cmsWidgetFieldMappings.DURATION
|
||||
);
|
||||
|
||||
const mobileDurationTime = getDisplayTextForDurationLength(
|
||||
this.estimatedServiceMinutesMinimum,
|
||||
this.estimatedServiceMinutesMaximum
|
||||
);
|
||||
|
||||
if (this.estimatedServiceMinutesMinimum && this.estimatedServiceMinutesMaximum) {
|
||||
return `${mobileDurationTextWithoutTime} ${mobileDurationTime}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
durationTextBlockCopy() {
|
||||
if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||
return null;
|
||||
return this.mobileDurationText;
|
||||
} else if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
|
||||
return this.inshopDurationText;
|
||||
} else if (
|
||||
|
|
@ -359,8 +381,8 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
initializeComponent(initialData) {
|
||||
this.setCalendarData(initialData);
|
||||
async initializeComponent(initialData) {
|
||||
await this.setCalendarData(initialData);
|
||||
this.$refs.timeSlotModalQuestion.initializeComponent();
|
||||
},
|
||||
fireDateSelectedEvent(event, date) {
|
||||
|
|
@ -555,6 +577,8 @@ export default {
|
|||
endDateString: initialViewEndDate,
|
||||
providerNumber: config.providerNumber,
|
||||
zipCode: config.zipCode,
|
||||
includeMobileTimeSlots: config.includeMobileTimeSlots,
|
||||
includeInshopTimeSlots: config.includeInshopTimeSlots,
|
||||
});
|
||||
resolve(response);
|
||||
});
|
||||
|
|
@ -574,6 +598,11 @@ export default {
|
|||
});
|
||||
},
|
||||
async setCalendarData(config = {}) {
|
||||
this.isLoading = true;
|
||||
this.selectableDatesInshop = [];
|
||||
this.selectableDatesMobile = [];
|
||||
this.months = [];
|
||||
|
||||
this.hideSomeDaysForInitialView = config.hideSomeDaysForInitialView;
|
||||
const hideSecondMonth = config.hideSecondMonth;
|
||||
const direction = config.calendarViewDirection;
|
||||
|
|
|
|||
|
|
@ -152,7 +152,6 @@ export default {
|
|||
border: 1px solid $gray-500;
|
||||
border-radius: 0.5rem;
|
||||
min-height: 3rem;
|
||||
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.2);
|
||||
@include media-breakpoint-up(md) {
|
||||
min-height: 3.5rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,18 +66,19 @@ export default {
|
|||
.btn {
|
||||
&.btn-primary {
|
||||
position: relative;
|
||||
background: $red;
|
||||
background: linear-gradient(270deg, $blue 0%, $blue-800 100%);
|
||||
border: none;
|
||||
border-radius: $border-radius-lg;
|
||||
color: $white;
|
||||
justify-content: center;
|
||||
font-weight: 500;
|
||||
@media (hover: hover) {
|
||||
background: $red;
|
||||
background: linear-gradient(270deg, $blue 0%, $blue-800 100%);
|
||||
}
|
||||
&:focus {
|
||||
box-shadow:
|
||||
0 0 0 3px,
|
||||
0 0 0 5.5px $red;
|
||||
0 0 0 5.5px $blue-700;
|
||||
}
|
||||
&:focus, // Mouse, touch, stylus focus
|
||||
&:focus-visible {
|
||||
|
|
@ -85,26 +86,27 @@ export default {
|
|||
outline: none;
|
||||
box-shadow:
|
||||
0 0 0 3px,
|
||||
0 0 0 5.5px $red;
|
||||
0 0 0 5.5px $blue-700;
|
||||
color: $white;
|
||||
background: $red;
|
||||
background: linear-gradient(270deg, rgba(6, 87, 124, 1) 0%, rgba(6, 87, 124, 1) 100%);
|
||||
}
|
||||
&:disabled {
|
||||
background: $gray-200 !important;
|
||||
background: $red !important;
|
||||
background: linear-gradient(270deg, $gray-200 0%, $gray-200 100%) !important;
|
||||
color: $gray-600 !important;
|
||||
font-weight: 400;
|
||||
height: 48px;
|
||||
border: none;
|
||||
border-radius: $border-radius-lg;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
&.has-loader {
|
||||
color: $white;
|
||||
background: $red;
|
||||
background: $blue-700;
|
||||
box-shadow:
|
||||
0 0 0 3px,
|
||||
0 0 0 5.5px $red;
|
||||
0 0 0 5.5px $blue-700;
|
||||
pointer-events: none;
|
||||
}
|
||||
&.delay {
|
||||
|
|
@ -115,7 +117,8 @@ export default {
|
|||
&.btn-secondary {
|
||||
position: relative;
|
||||
background: transparent;
|
||||
border: 1px solid $red;
|
||||
border: 1px solid $blue;
|
||||
border-radius: $border-radius-lg;
|
||||
color: $blue;
|
||||
font-weight: 500;
|
||||
transition: all 150ms linear;
|
||||
|
|
@ -130,7 +133,7 @@ export default {
|
|||
outline: none;
|
||||
box-shadow:
|
||||
0 0 0 3px $white,
|
||||
0 0 0 5.5px $red;
|
||||
0 0 0 5.5px $blue-700;
|
||||
color: $white;
|
||||
@include blue-gradient;
|
||||
}
|
||||
|
|
@ -139,12 +142,14 @@ export default {
|
|||
color: $gray-550 !important;
|
||||
font-weight: 400;
|
||||
height: 48px;
|
||||
border: 1px solid $red;
|
||||
border: 1px solid $gray-550;
|
||||
border-radius: $border-radius-lg;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
&.has-loader {
|
||||
color: $white;
|
||||
@include blue-gradient;
|
||||
pointer-events: none;
|
||||
}
|
||||
&.delay {
|
||||
|
|
|
|||
|
|
@ -182,11 +182,11 @@ export default {
|
|||
};
|
||||
},
|
||||
methods: {
|
||||
focusSearchInput() {
|
||||
focusSearchInput(event) {
|
||||
// Focus cursor in input when search icon is clicked
|
||||
const field = document.querySelector("input");
|
||||
field.focus();
|
||||
this.$emit("search-icon-click");
|
||||
this.$emit("search-icon-click", event);
|
||||
},
|
||||
async imageChanged(e) {
|
||||
let file = e.target.files[0];
|
||||
|
|
@ -337,7 +337,6 @@ export default {
|
|||
border-radius: 0.5rem;
|
||||
min-height: 3rem;
|
||||
padding: 0.75rem 1rem;
|
||||
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.2);
|
||||
@include media-breakpoint-up(md) {
|
||||
padding: 1rem;
|
||||
min-height: 3.5rem;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
href="javascript:void(0)"
|
||||
class="col d-flex justify-content-between py-0">
|
||||
<span class="label">{{ OrderDetailsText }}</span>
|
||||
<span class="label amount-due">
|
||||
<span class="label amount-due larger">
|
||||
{{ getLineItemAmount(amountDue, showCoverageAsPending) }}
|
||||
</span>
|
||||
</a>
|
||||
|
|
@ -83,16 +83,13 @@
|
|||
:text="removeLinkText"
|
||||
href="javascript:void(0)"
|
||||
@click-event="removeItem(cartItem.cartItemType, cartItem.category)" />
|
||||
<textLink
|
||||
v-else-if="
|
||||
cartItem == recycleFeeCartItem && recyclingModalCmsWidgetName
|
||||
"
|
||||
linkType="text"
|
||||
:text="recycleFeeCartItem.name"
|
||||
href="javascript:void(0)"
|
||||
@click-event="openModal(recyclingModalCmsWidgetName)" />
|
||||
<span v-else-if="cartItem == recycleFeeCartItem">
|
||||
{{ recycleFeeCartItem.name }}
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
@click="openModal(recyclingModalCmsWidgetName)">
|
||||
<img :src="infoIcon" alt="Info Icon" class="info-icon" />
|
||||
</a>
|
||||
</span>
|
||||
<span
|
||||
v-if="
|
||||
|
|
@ -1150,6 +1147,9 @@ export default {
|
|||
var amtPaid = +this.subTotal + +this.salesTax;
|
||||
return amtPaid;
|
||||
},
|
||||
infoIcon() {
|
||||
return require(`@/assets/img/icons/question-mark-icon-blue.svg`);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
textBlock,
|
||||
|
|
@ -1184,6 +1184,10 @@ export default {
|
|||
}
|
||||
.amount-due {
|
||||
color: $green;
|
||||
|
||||
&.larger {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
.cart-panel {
|
||||
max-height: 0;
|
||||
|
|
@ -1197,29 +1201,13 @@ export default {
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem 1.5rem;
|
||||
&:nth-last-child(-n + 3) {
|
||||
background-color: $green-100;
|
||||
}
|
||||
&:last-child {
|
||||
background-color: $green;
|
||||
color: #ffffff;
|
||||
background-color: $gray-100;
|
||||
> span:last-of-type {
|
||||
color: $green;
|
||||
}
|
||||
}
|
||||
}
|
||||
.deductible {
|
||||
background-color: $gray-100;
|
||||
}
|
||||
.service-type {
|
||||
background-color: $gray-100;
|
||||
}
|
||||
.deductible ~ .service-type {
|
||||
background: $white;
|
||||
}
|
||||
.service-type ~ .packaged-cart-item {
|
||||
background-color: $gray-100;
|
||||
}
|
||||
.deductible ~ .packaged-cart-item {
|
||||
background: $white;
|
||||
}
|
||||
.struck-out-price {
|
||||
text-decoration: line-through;
|
||||
padding-left: 0.5rem;
|
||||
|
|
@ -1247,28 +1235,11 @@ export default {
|
|||
}
|
||||
}
|
||||
.non-packaged-cart-item {
|
||||
&.odd {
|
||||
background: $gray-100;
|
||||
}
|
||||
}
|
||||
.deductible ~ .non-packaged-cart-item {
|
||||
&.odd {
|
||||
background: $white;
|
||||
}
|
||||
}
|
||||
.non-packaged-cart-item {
|
||||
&.even {
|
||||
background-color: $white;
|
||||
}
|
||||
a {
|
||||
line-height: 1.625;
|
||||
}
|
||||
}
|
||||
.deductible ~ .non-packaged-cart-item {
|
||||
&.even {
|
||||
background-color: $gray-100;
|
||||
}
|
||||
}
|
||||
|
||||
// Custom order/wrap for removable nonpackaged Cart Items
|
||||
.removable-cart-item {
|
||||
flex-wrap: wrap;
|
||||
|
|
@ -1296,8 +1267,7 @@ export default {
|
|||
color: $black;
|
||||
}
|
||||
.sub-total {
|
||||
border-top: 1px solid $green;
|
||||
background-color: $green-100;
|
||||
border-top: 1px solid $gray-600;
|
||||
}
|
||||
.service-type,
|
||||
.deductible {
|
||||
|
|
@ -1316,11 +1286,11 @@ export default {
|
|||
&:after {
|
||||
content: "";
|
||||
transition: all 0.5s ease;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24'%3E%3Cpath fill='%230070D1' d='M12 0a12 12 0 1 0 0 24 12 12 0 0 0 0-24Zm6.113 11.28-5.52 5.52a.84.84 0 0 1-1.186 0l-5.52-5.52a.843.843 0 1 1 1.186-1.2L12 15.012l4.927-4.932a.843.843 0 1 1 1.186 1.2Z'/%3E%3C/svg%3E");
|
||||
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;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 16px;
|
||||
height: 9px;
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
right: 0.75rem;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,15 @@
|
|||
<template>
|
||||
<div class="funnel-footer">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col d-flex justify-content-center justify-content-md-start">
|
||||
<img class="skyline" src="~@/assets/img/skyline-van.svg" alt="" />
|
||||
</div>
|
||||
<div class="col d-none d-md-flex justify-content-end align-items-end">
|
||||
<img class="neighborhood" src="~@/assets/img/neighborhood.svg" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-inner-wrapper">
|
||||
<textLink
|
||||
linkType="newWindowLink"
|
||||
|
|
@ -54,6 +64,13 @@ export default {
|
|||
margin-top: auto;
|
||||
padding: 0 0 2rem 0;
|
||||
|
||||
.skyline {
|
||||
height: 96px;
|
||||
}
|
||||
.neighborhood {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.25rem;
|
||||
|
|
@ -76,16 +93,12 @@ export default {
|
|||
:deep(a) {
|
||||
text-decoration: none;
|
||||
&.new-window-link {
|
||||
font-family: Urbanist, Arial, Helvetica, sans-serif;
|
||||
margin: 0 0.75rem;
|
||||
color: $gray-600;
|
||||
font-weight: 400;
|
||||
}
|
||||
&.navigation-link {
|
||||
font-family: Urbanist, Arial, Helvetica, sans-serif;
|
||||
margin: 0 0.75rem;
|
||||
color: $gray-600;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,8 @@
|
|||
<template>
|
||||
<div class="funnel-header" v-if="imageSrc">
|
||||
<div class="d-flex w-100">
|
||||
<progress-bar :page="$route.name" />
|
||||
</div>
|
||||
<div class="container d-flex">
|
||||
<div class="site-logo">
|
||||
<a href="https://safelite.com">
|
||||
<img class="logo-image img-fluid" :src="imageSrc" alt="Safelite logo" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="button-container webchat">
|
||||
<span class="webchat d-flex">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="button-container">
|
||||
<span class="webchat">
|
||||
<button
|
||||
v-show="shouldShowWebchatButton"
|
||||
v-on:click="webchatClicked"
|
||||
|
|
@ -18,9 +10,17 @@
|
|||
class="chat-button"></button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="button-container">
|
||||
<menuModal />
|
||||
<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">
|
||||
<progress-bar :page="$route.name" />
|
||||
</div>
|
||||
</div>
|
||||
<template v-for="globalAlert in globalAlertMessages" :key="globalAlert.id">
|
||||
|
|
@ -197,39 +197,23 @@ export default {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.funnel-header {
|
||||
.container {
|
||||
padding: 1rem 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@include media-breakpoint-up(md) {
|
||||
padding: 2rem 0.75rem;
|
||||
}
|
||||
}
|
||||
position: relative;
|
||||
padding: 0 0 1rem 0;
|
||||
padding: 1rem 0;
|
||||
|
||||
.button-container {
|
||||
&.webchat {
|
||||
margin-right: 1rem;
|
||||
@include media-breakpoint-up(md) {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.site-logo {
|
||||
margin-right: auto;
|
||||
width: 1.5rem;
|
||||
height: 1.625rem;
|
||||
}
|
||||
|
||||
.logo-image {
|
||||
width: 100px;
|
||||
height: auto;
|
||||
width: auto;
|
||||
height: 1.1875rem;
|
||||
margin: 0 auto;
|
||||
@include media-breakpoint-up(md) {
|
||||
width: 165px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.menu-modal-container) {
|
||||
display: none; // This is temporary until the hamburger menu is re-implemented for use with the progress bar
|
||||
}
|
||||
.webchat {
|
||||
.chat-button {
|
||||
width: 1.5rem;
|
||||
|
|
|
|||
|
|
@ -6,13 +6,9 @@
|
|||
:class="[isActive ? 'active' : '']"
|
||||
@click="toggleModal"
|
||||
aria-label="Hamburger Menu (modal window)">
|
||||
<span v-if="isActive">Close</span>
|
||||
<span v-else>Step {{ currentStepNumber }} of 4</span>
|
||||
<div class="bars ms-2">
|
||||
<div class="bar1"></div>
|
||||
<div class="bar2"></div>
|
||||
<div class="bar3"></div>
|
||||
</div>
|
||||
<div class="bar1"></div>
|
||||
<div class="bar2"></div>
|
||||
<div class="bar3"></div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -22,45 +18,47 @@
|
|||
tabindex="-1"
|
||||
aria-labelledby="footerModalLabel"
|
||||
aria-hidden="true"
|
||||
v-on="{ 'show.bs.modal': show, 'hide.bs.modal': hide }"
|
||||
@click.self="closeModal">
|
||||
v-on="{ 'show.bs.modal': show, 'hide.bs.modal': hide }">
|
||||
<div class="modal-dialog modal-fullscreen">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header visually-hidden">
|
||||
<h5 class="modal-title" id="footerModalLabel">Footer Navigation</h5>
|
||||
</div>
|
||||
<div class="modal-body d-flex flex-column">
|
||||
<div class="step-grid">
|
||||
<div class="bar-container">
|
||||
<progress-bar class="bar-dots" :page="$route.name" :vertical="true" />
|
||||
<span class="dot1"></span>
|
||||
<span class="dot2"></span>
|
||||
<span class="dot3"></span>
|
||||
<span class="dot4"></span>
|
||||
</div>
|
||||
<div class="d-flex flex-column justify-content-between">
|
||||
<div class="step-containers">
|
||||
<span class="step-number">Step 1 of 4</span>
|
||||
<br />
|
||||
<span class="step-name">Vehicle</span>
|
||||
</div>
|
||||
<div class="step-containers">
|
||||
<span class="step-number">Step 2 of 4</span>
|
||||
<br />
|
||||
<span class="step-name">Quote</span>
|
||||
</div>
|
||||
<div class="step-containers">
|
||||
<span class="step-number">Step 3 of 4</span>
|
||||
<br />
|
||||
<span class="step-name">Schedule</span>
|
||||
</div>
|
||||
<div class="step-containers">
|
||||
<span class="step-number">Step 4 of 4</span>
|
||||
<br />
|
||||
<span class="step-name">Review</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<textLink
|
||||
linkType="newWindowLink"
|
||||
text="Terms of service"
|
||||
href="//www.safelite.com/terms-of-service"
|
||||
target="_blank" />
|
||||
<textLink
|
||||
linkType="newWindowLink"
|
||||
text="Your privacy choices"
|
||||
href="//www.safelite.com/privacy-center"
|
||||
target="_blank">
|
||||
<template v-slot:after-text>
|
||||
<img
|
||||
class="ccpa-icon"
|
||||
src="~@/assets/img/icons/ccpa-icon.svg"
|
||||
alt="Your privacy choices" />
|
||||
</template>
|
||||
</textLink>
|
||||
<textLink
|
||||
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>
|
||||
<div class="modal-footer d-flex justify-content-start">
|
||||
© {{ new Date().getFullYear() }} Safelite Group
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -68,9 +66,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import textLink from "@/ux-components/text-link/text-link";
|
||||
import { Modal } from "bootstrap";
|
||||
import { getProgressBarStep } from "@/constants/progress-bar-mapper";
|
||||
import progressBar from "@/fmg-components/funnel-header/progress-bar/progress-bar";
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
|
||||
export default {
|
||||
|
|
@ -103,13 +100,8 @@ export default {
|
|||
self.isActive = false;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
currentStepNumber() {
|
||||
return getProgressBarStep(this.$route.name);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
progressBar,
|
||||
textLink,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -119,92 +111,49 @@ export default {
|
|||
button {
|
||||
border: none;
|
||||
&.menu-button {
|
||||
width: auto;
|
||||
height: auto;
|
||||
border-radius: 1.5rem;
|
||||
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.2);
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.2);
|
||||
background-color: $white;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0.15rem 0.5rem; //Required to prevent 'squish' on iPhone
|
||||
padding: 0; //Required to prevent 'squish' on iPhone
|
||||
z-index: 1050;
|
||||
@include media-breakpoint-up(md) {
|
||||
padding: 0.25rem 1rem;
|
||||
.bar1,
|
||||
.bar2,
|
||||
.bar3 {
|
||||
width: 14px;
|
||||
height: 2px;
|
||||
background-color: $blue;
|
||||
margin: 1px 0;
|
||||
transition: 0.25s;
|
||||
}
|
||||
&.active .bar1 {
|
||||
transform: rotate(-45deg) translate(-3px, 8px);
|
||||
transform: rotate(-45deg) translate(-3px, 3px);
|
||||
}
|
||||
&.active .bar2 {
|
||||
opacity: 0;
|
||||
}
|
||||
&.active .bar3 {
|
||||
transform: rotate(45deg) translate(-3px, -8px);
|
||||
}
|
||||
.bars {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-direction: column;
|
||||
scale: 0.75;
|
||||
.bar1,
|
||||
.bar2,
|
||||
.bar3 {
|
||||
width: 1.5rem;
|
||||
height: 2px;
|
||||
background-color: $blue;
|
||||
margin: 1px 0;
|
||||
transition: 0.25s;
|
||||
}
|
||||
}
|
||||
.step-numbers {
|
||||
font-size: 0.875rem;
|
||||
margin-right: 0.5rem;
|
||||
@include media-breakpoint-up(md) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
transform: rotate(45deg) translate(-3px, -3px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.modal {
|
||||
&.menu-modal {
|
||||
.modal-dialog {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: calc(100% - 68px);
|
||||
width: 100vw; // or set a max-width if you want a drawer effect
|
||||
max-width: 294px;
|
||||
margin: 0;
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
z-index: 1050;
|
||||
@include media-breakpoint-up(md) {
|
||||
height: calc(100% - 102px);
|
||||
top: 102px;
|
||||
}
|
||||
}
|
||||
&.show .modal-dialog {
|
||||
transform: translateX(0);
|
||||
}
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
height: calc(100% - 68px);
|
||||
top: 68px;
|
||||
//left: auto;
|
||||
height: calc(100% - 56px);
|
||||
top: 56px;
|
||||
border-top: 1px solid $gray-300;
|
||||
overflow-x: visible;
|
||||
overflow-y: visible;
|
||||
@include media-breakpoint-up(md) {
|
||||
height: calc(100% - 102px);
|
||||
top: 102px;
|
||||
}
|
||||
.modal-body {
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.ccpa-icon {
|
||||
width: 2.0625rem;
|
||||
height: 1rem;
|
||||
|
|
@ -213,48 +162,6 @@ export default {
|
|||
:deep(.navigation-link) {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.step-grid {
|
||||
display: grid;
|
||||
max-width: 140px;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
.step-containers {
|
||||
line-height: 1;
|
||||
}
|
||||
.bar-container {
|
||||
width: 24px;
|
||||
position: relative;
|
||||
.dot1,
|
||||
.dot2,
|
||||
.dot3,
|
||||
.dot4 {
|
||||
position: absolute;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
background-color: $white;
|
||||
border-radius: 50rem;
|
||||
border: 1px solid $gray;
|
||||
left: 1px;
|
||||
}
|
||||
.dot1 {
|
||||
top: 1px;
|
||||
}
|
||||
.dot2 {
|
||||
top: 114px;
|
||||
}
|
||||
.dot3 {
|
||||
top: 227px;
|
||||
}
|
||||
.dot4 {
|
||||
top: 332px;
|
||||
}
|
||||
}
|
||||
.step-number {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.step-name {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.modal-fullscreen {
|
||||
width: 100vw;
|
||||
|
|
@ -283,30 +190,23 @@ export default {
|
|||
align-items: center;
|
||||
padding: 0; //Required to prevent 'squish' on iPhone
|
||||
z-index: 1056;
|
||||
.bars {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-direction: column;
|
||||
.bar1,
|
||||
.bar2,
|
||||
.bar3 {
|
||||
width: 1.5rem;
|
||||
height: 2px;
|
||||
background-color: $blue;
|
||||
margin: 1px 0;
|
||||
transition: 0.25s;
|
||||
}
|
||||
&.active .bar1 {
|
||||
transform: rotate(-45deg) translate(-3px, 8px);
|
||||
}
|
||||
&.active .bar2 {
|
||||
opacity: 0;
|
||||
}
|
||||
&.active .bar3 {
|
||||
transform: rotate(45deg) translate(-3px, -8px);
|
||||
}
|
||||
.bar1,
|
||||
.bar2,
|
||||
.bar3 {
|
||||
width: 14px;
|
||||
height: 2px;
|
||||
background-color: $blue;
|
||||
margin: 1px 0;
|
||||
transition: 0.25s;
|
||||
}
|
||||
&.active .bar1 {
|
||||
transform: rotate(-45deg) translate(-3px, 3px);
|
||||
}
|
||||
&.active .bar2 {
|
||||
opacity: 0;
|
||||
}
|
||||
&.active .bar3 {
|
||||
transform: rotate(45deg) translate(-3px, -3px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div :class="barClass">
|
||||
<div class="progress-bar-outer">
|
||||
<div class="progress-bar-inner" :style="progressStyle"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -18,10 +18,6 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
vertical: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -49,20 +45,9 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
progressStyle() {
|
||||
if (this.vertical) {
|
||||
return {
|
||||
height: this.displayedProgress + "%",
|
||||
width: "100%",
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
width: this.displayedProgress + "%",
|
||||
height: "100%",
|
||||
};
|
||||
}
|
||||
},
|
||||
barClass() {
|
||||
return this.vertical ? "progress-bar-outer-vertical" : "progress-bar-outer";
|
||||
return {
|
||||
width: this.displayedProgress + "%",
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -70,43 +55,50 @@ export default {
|
|||
<style lang="scss">
|
||||
.progress-bar-outer {
|
||||
background: $blue-100;
|
||||
height: 5px;
|
||||
height: 10px;
|
||||
position: relative;
|
||||
border-radius: 0;
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
margin-top: 0;
|
||||
margin-top: 1rem;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
.progress-bar-inner {
|
||||
transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1); /* Smooth width transition */
|
||||
will-change: width;
|
||||
height: 100%;
|
||||
background-color: $red;
|
||||
height: 10px;
|
||||
background-color: $blue;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-radius: 0;
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-bar-outer-vertical {
|
||||
background: $blue-100;
|
||||
width: 24px;
|
||||
height: 356px;
|
||||
position: relative;
|
||||
border-radius: 0;
|
||||
margin-top: 0;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
overflow: hidden;
|
||||
border-radius: 50rem;
|
||||
.progress-bar-inner {
|
||||
transition: height 0.5s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
will-change: height !important;
|
||||
progress {
|
||||
height: 10px;
|
||||
width: 100%;
|
||||
background-color: $red;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
border-radius: 50rem;
|
||||
|
||||
/* 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>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
<template>
|
||||
<div class="funnel-sub-header">
|
||||
<div class="d-flex align-items-center overflow-hidden">
|
||||
<h5 class="fw-normal mb-0" :class="headerColor">
|
||||
<div
|
||||
class="d-flex align-items-center container-fluid overflow-hidden"
|
||||
:class="[textAlignment]">
|
||||
<h5 class="text-center fw-normal mb-0" :class="headerColor">
|
||||
<span>
|
||||
{{ text }}
|
||||
</span>
|
||||
|
|
@ -11,9 +13,13 @@
|
|||
@click-event="clickEvent" />
|
||||
</h5>
|
||||
</div>
|
||||
<div v-if="subText" class="d-flex align-self-center overflow-hidden mt-1">
|
||||
<p class="small fw-normal sub-text">
|
||||
<span v-html="boldedSubHeaderText"></span>
|
||||
<div
|
||||
v-if="subText"
|
||||
class="d-flex align-items-center justify-content-center container-fluid overflow-hidden mt-1">
|
||||
<p class="text-center small fw-normal sub-text">
|
||||
<span>
|
||||
{{ subText }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -31,17 +37,21 @@ export default {
|
|||
type: String,
|
||||
default: "dark-header",
|
||||
},
|
||||
leftAlignHeader: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
buttonBack,
|
||||
},
|
||||
computed: {
|
||||
boldedSubHeaderText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "HeaderSubText");
|
||||
},
|
||||
text() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "HeaderText");
|
||||
},
|
||||
textAlignment() {
|
||||
return this.leftAlignHeader ? "justify-content-start" : "justify-content-center";
|
||||
},
|
||||
subText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "HeaderSubText");
|
||||
},
|
||||
|
|
@ -58,12 +68,8 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.bolded-words) {
|
||||
font-family: UrbanistSemibold, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
h5 {
|
||||
line-height: 32px;
|
||||
font-size: 1.25rem;
|
||||
|
||||
button {
|
||||
color: inherit;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,31 @@
|
|||
<template>
|
||||
<div :class="`questions-page`">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<div class="container page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4 mt-4">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7 question-chain-wrapper">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<alert
|
||||
ref="alertFewMoreQuestions"
|
||||
cmsWidgetName="alertWidget"
|
||||
class="my-5"
|
||||
alertClass="alert-warning"
|
||||
:manualHeadline="alertFewMoreQuestionsHeader"
|
||||
:manualCopy="alertFewMoreQuestionsCopy"
|
||||
v-bind:isDismissible="false" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4 question-chain-wrapper">
|
||||
<div v-for="(questionsDatum, i) in questionsData" :key="questionsDatum.key">
|
||||
<questionChain
|
||||
ref="questionChain"
|
||||
|
|
@ -38,6 +54,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
import questionChain from "@/digital-components/question-chain/question-chain";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
|
|
@ -79,6 +96,7 @@ export default {
|
|||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
alert,
|
||||
questionChain,
|
||||
funnelSubHeader,
|
||||
navbar,
|
||||
|
|
@ -96,7 +114,7 @@ export default {
|
|||
}
|
||||
.questions-page {
|
||||
.question-text {
|
||||
margin-bottom: 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
span {
|
||||
text-align: left;
|
||||
|
|
|
|||
|
|
@ -118,14 +118,13 @@ export default {
|
|||
margin-bottom: 1.5rem;
|
||||
order: 2;
|
||||
text-align: center;
|
||||
background: $gray-100;
|
||||
background: $blue-100;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 3rem;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.2);
|
||||
|
||||
&.progress-saved {
|
||||
background: none;
|
||||
|
|
@ -136,13 +135,14 @@ export default {
|
|||
position: relative;
|
||||
color: $blue;
|
||||
border: none;
|
||||
font-family: UrbanistSemibold;
|
||||
font-weight: 900;
|
||||
font-size: 0.875rem;
|
||||
text-align: left;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
height: auto;
|
||||
text-decoration: none;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 0.25rem;
|
||||
line-height: 1rem;
|
||||
padding: 1rem;
|
||||
margin: 0 auto;
|
||||
|
|
@ -154,6 +154,7 @@ export default {
|
|||
|
||||
&.btn {
|
||||
&.btn-secondary {
|
||||
font-weight: 900;
|
||||
border: none;
|
||||
&:hover,
|
||||
&:active,
|
||||
|
|
@ -182,17 +183,6 @@ export default {
|
|||
// fixes flicker while transitioning between states
|
||||
transition: background 0s 0s ease-in-out;
|
||||
}
|
||||
|
||||
&.open-save-progress-button {
|
||||
span {
|
||||
color: $gray-600;
|
||||
font-family: UrbanistRegular;
|
||||
.highlighted-words {
|
||||
font-family: UrbanistSemibold;
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal.modal-component .modal-dialog .modal-content {
|
||||
|
|
|
|||
|
|
@ -5,14 +5,15 @@
|
|||
ref="theForm"
|
||||
v-slot="{ meta }"
|
||||
autocomplete="off">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<div class="container page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<funnelSubHeader
|
||||
cmsWidgetName="FunnelSubHeaderWidget"
|
||||
ref="funnelSubHeader"
|
||||
class="mb-7" />
|
||||
<div class="container-fluid page-container-grouped-styles" id="address-lookup">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4 mt-4">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" ref="funnelSubHeader" />
|
||||
|
||||
<customerQuestions
|
||||
ref="customerQuestions"
|
||||
|
|
|
|||
|
|
@ -9,15 +9,79 @@
|
|||
<img :src="ScheduleConfirmationImage" />
|
||||
<span v-html="ScheduleConfirmationText"></span>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<div class="header-container">
|
||||
<img :src="ScheduleConfirmationImage" />
|
||||
<span v-html="ScheduleConfirmationText"></span>
|
||||
</div>
|
||||
|
||||
<div class="emailtext mb-5" v-html="ConfirmationEmailText"></div>
|
||||
<div class="emailtext" v-html="ConfirmationEmailText"></div>
|
||||
|
||||
<div class="main">
|
||||
<div></div>
|
||||
<div class="main">
|
||||
<div></div>
|
||||
|
||||
<div class="scheduleText">
|
||||
<p>{{ ScheduleDateFormatted }}</p>
|
||||
<p>{{ ScheduleTimeFormatted }}</p>
|
||||
<div class="scheduleText">
|
||||
<p>{{ ScheduleDateFormatted }}</p>
|
||||
<p>{{ ScheduleTimeFormatted }}</p>
|
||||
</div>
|
||||
|
||||
<addToCalendar
|
||||
mobileWidgetName="AddToCalendar_Mobile"
|
||||
inShopWidgetName="AddToCalendar_InShop"
|
||||
dropOffWidgetName="AddToCalendar_DropOff"
|
||||
overnightDropOffWidgetName="AddToCalendar_OvernightDropOff"
|
||||
allDayDropOffWidgetName="AddToCalendar_AllDayDropOff"
|
||||
sameDayDropOffWidgetName="AddToCalendar_SameDayDropOff"
|
||||
:serviceLocationFullAddress="ServiceLocationFullAddress"
|
||||
:providerFullAddress="ProviderFullAddress"
|
||||
:appointmentType="AppointmentType"
|
||||
:scheduleDate="ScheduleDate"
|
||||
:scheduleStartTime="ScheduleStartTime"
|
||||
:scheduleEndTime="ScheduleEndTime" />
|
||||
|
||||
<div class="appointment-text" v-html="AppointmentWordingText"></div>
|
||||
|
||||
<textBlock
|
||||
:customText="AppointmentDuration"
|
||||
justifyText="center"
|
||||
typeStyle="medium"
|
||||
class="duration-text-block" />
|
||||
</div>
|
||||
|
||||
<div class="mt-3" v-if="shouldDisplayFosterLove">
|
||||
<donationBlock
|
||||
cmsWidgetName="DonationWidget"
|
||||
v-model="donationAmount"
|
||||
:donationValues="donationValues"
|
||||
:showDonationSuccess="showDonationSuccess"
|
||||
:showDonationError="showDonationError"
|
||||
@DonationAddedEvent="addDonationToOrder"
|
||||
ref="donationBlock" />
|
||||
</div>
|
||||
|
||||
<hr class="mt-4 mb-0" />
|
||||
|
||||
<cart
|
||||
v-if="ShowCart"
|
||||
:damage="damageInfo"
|
||||
:availableVaps="vaps"
|
||||
:allowItemRemoval="false"
|
||||
v-model="lineItemsWithoutDonation"
|
||||
:donationCartItem="donationLineItem"
|
||||
:showAsPaid="isPia"
|
||||
servicePackageOptionsCmsName="ServicePackageTitle"
|
||||
:isInsurance="isInsurance"
|
||||
:insuranceDeductible="currentDeductible"
|
||||
:insuranceCompanyName="insuranceCompanyName"
|
||||
:showInsuranceCoverageAs="showInsuranceCoverageAs"
|
||||
:shouldHideRecalibration="shouldHideRecalibration"
|
||||
:isExpandedOnLoad="false"
|
||||
:isItac="isItac"
|
||||
:isNoComp="isNoComp"
|
||||
:isMSRFeeApplicable="isMSRFeeApplicable" />
|
||||
|
||||
<hr class="mb-5" />
|
||||
</div>
|
||||
|
||||
<addToCalendar
|
||||
|
|
@ -635,7 +699,7 @@ export default {
|
|||
}
|
||||
|
||||
.emailtext {
|
||||
padding: 0.5rem 0 0;
|
||||
padding: 0 0 1.5em;
|
||||
|
||||
p {
|
||||
font-size: 0.875rem;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<div class="container page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-4 mb-5" />
|
||||
<textboxQuestion
|
||||
isRequired
|
||||
|
|
@ -43,6 +47,8 @@
|
|||
cmsWidgetName="TextMeQuestionWidget"
|
||||
v-model="isSmsOptIn" />
|
||||
|
||||
<techNotes v-model="techNotes" :textAreaLabelCopy="textAreaLabelCopy" />
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
|
|
@ -68,6 +74,7 @@ import textboxQuestion from "@/digital-components/textbox-question/textbox-quest
|
|||
import phoneNumberQuestion from "@/digital-components/phone-number-question/phone-number-question";
|
||||
import textBlock from "@/digital-components/text-block/text-block";
|
||||
import checkboxQuestion from "@/digital-components/checkbox-question/checkbox-question";
|
||||
import techNotes from "@/layouts/customer-details/tech-notes/tech-notes";
|
||||
//Supporting files
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
|
|
@ -212,6 +219,7 @@ export default {
|
|||
textBlock,
|
||||
phoneNumberQuestion,
|
||||
checkboxQuestion,
|
||||
techNotes,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<div class="container page-container-grouped-styles estimate">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4 mt-4">
|
||||
<funnelSubHeader class="pb-4" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div>
|
||||
<div class="vinlookupquestion" v-html="this.VinLookupQuestionText"></div>
|
||||
<div v-html="this.VinLookupQuestionBodyText2"></div>
|
||||
<div
|
||||
class="select-an-option"
|
||||
v-html="this.VinLookupQuestionFooterText"></div>
|
||||
<buttonQuestion
|
||||
cmsWidgetName="VinLookupMethod"
|
||||
:answers="answersFromCms"
|
||||
|
|
@ -28,8 +29,6 @@
|
|||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
|
||||
<div class="text-center" v-html="this.VinLookupQuestionFooterText2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -40,6 +39,7 @@
|
|||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import buttonQuestion from "@/digital-components/button-question/button-question";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
|
||||
|
|
@ -229,18 +229,10 @@ export default {
|
|||
VinLookupQuestionText() {
|
||||
return this.getCmsContent("VinLookupQuestion", "BodyText");
|
||||
},
|
||||
VinLookupQuestionBodyText2() {
|
||||
return this.getCmsContent("VinLookupQuestion", "BodyText2");
|
||||
},
|
||||
VinLookupQuestionFooterText() {
|
||||
return this.getCmsContent("VinLookupQuestion", "FooterText");
|
||||
},
|
||||
VinLookupQuestionFooterText2() {
|
||||
return this.getCmsContent("VinLookupQuestion", "FooterText2");
|
||||
},
|
||||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelSubHeader,
|
||||
navbar,
|
||||
buttonQuestion,
|
||||
Form,
|
||||
|
|
@ -251,12 +243,14 @@ export default {
|
|||
|
||||
<style lang="scss">
|
||||
.vinlookupquestion p {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.select-an-option {
|
||||
font-family: UrbanistSemibold;
|
||||
font-weight: 600;
|
||||
.vinlookupquestion strong {
|
||||
font-weight: 500;
|
||||
color: $black;
|
||||
}
|
||||
.vinlookupquestion p:nth-child(2) {
|
||||
font-size: 0.875rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<div class="container page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-4" />
|
||||
|
||||
<div v-if="originalList.length > 0">
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<div class="container page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4 mt-4">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<textboxQuestion
|
||||
|
|
|
|||
|
|
@ -112,7 +112,6 @@ export default {
|
|||
border: 1px solid $gray-500;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.2);
|
||||
|
||||
span {
|
||||
&.small {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<loadingModal notFullScreen ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<div class="container payment-method">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<div class="container-fluid payment-method">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4 mt-4">
|
||||
<funnelSubHeader class="mb-5" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<hr class="my-0" />
|
||||
|
|
@ -53,7 +57,7 @@
|
|||
|
||||
<div
|
||||
v-if="isRecalibrationOnOrder && shouldHideRecalibration"
|
||||
class="questions-about-service mt-5">
|
||||
class="questions-about-service my-5">
|
||||
<textBlock
|
||||
cmsWidgetName="QuestionsAboutYourServiceWidget"
|
||||
justifyText="left"
|
||||
|
|
@ -63,7 +67,7 @@
|
|||
<div class="d-flex flex-row checkbox-group">
|
||||
<checkboxQuestion
|
||||
cmsWidgetName="RecalConfirmWidget"
|
||||
class="mb-3"
|
||||
class="mb-5"
|
||||
isRequired="true"
|
||||
v-model="isRecalAckOptIn"
|
||||
validationRules="recal-ack-required"
|
||||
|
|
@ -78,6 +82,13 @@
|
|||
:isInsurance="isInsurance"
|
||||
validationRules="payment-method-required" />
|
||||
|
||||
<alert
|
||||
v-if="showHavePaymentReadyAlert"
|
||||
:isDismissible="false"
|
||||
alertClass="alert-info"
|
||||
cmsWidgetName="NoPiaDisclaimerWidget"
|
||||
shouldScrollToOnMount="false" />
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
|
|
@ -759,6 +770,11 @@ export default {
|
|||
|
||||
return false;
|
||||
},
|
||||
showHavePaymentReadyAlert() {
|
||||
return (
|
||||
!this.showPaymentMethodQuestions && !this.forwardClicked && this.totalAmountDue > 0
|
||||
);
|
||||
},
|
||||
totalAmountDue() {
|
||||
return getAmountDue(this.lineItems);
|
||||
},
|
||||
|
|
@ -891,12 +907,6 @@ export default {
|
|||
margin: 0;
|
||||
}
|
||||
.payment-method {
|
||||
:deep(.almost-done) {
|
||||
font-family: UrbanistSemibold;
|
||||
}
|
||||
:deep(h5) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.recal-modal {
|
||||
:deep(.modal-body) {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -157,11 +157,11 @@ export default {
|
|||
&:after {
|
||||
content: "";
|
||||
transition: all 0.5s ease;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24'%3E%3Cpath fill='%230070D1' d='M12 0a12 12 0 1 0 0 24 12 12 0 0 0 0-24Zm6.113 11.28-5.52 5.52a.84.84 0 0 1-1.186 0l-5.52-5.52a.843.843 0 1 1 1.186-1.2L12 15.012l4.927-4.932a.843.843 0 1 1 1.186 1.2Z'/%3E%3C/svg%3E");
|
||||
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;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 16px;
|
||||
height: 9px;
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
right: 0.75rem;
|
||||
|
|
|
|||
|
|
@ -577,7 +577,7 @@ describe("payment.vue", () => {
|
|||
// Arrange
|
||||
const wrapper = setupMocks({});
|
||||
|
||||
const outerDiv = wrapper.find(".container");
|
||||
const outerDiv = wrapper.find(".container-fluid");
|
||||
const clickFn = jest.fn();
|
||||
outerDiv.element.addEventListener("click", clickFn);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
<template>
|
||||
<Form>
|
||||
<loadingModal notFullScreen ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4 p-0">
|
||||
<div>
|
||||
<alert
|
||||
ref="piaCCErrorAlert"
|
||||
|
|
|
|||
|
|
@ -6,9 +6,13 @@
|
|||
async></component>
|
||||
|
||||
<div>
|
||||
<span v-for="token in headerCopyTokens" :key="token" v-html="token"></span>
|
||||
<span
|
||||
class="callout"
|
||||
v-for="token in headerCopyTokens"
|
||||
:key="token"
|
||||
v-html="token"></span>
|
||||
</div>
|
||||
<div :id="showAfterpayExtendedPayOption ? 'extended-pay-option' : ''">
|
||||
<div>
|
||||
<span v-for="token in afterpayCopyTokens" :key="token">
|
||||
<span v-if="isAfterpayPriceToken(token)">{{ afterpayPrice }}</span>
|
||||
<span v-else-if="isInlineImageToken(token)">
|
||||
|
|
@ -78,15 +82,7 @@ export default {
|
|||
return splitCMSCopyOnBR(this.getCmsContent(this.cmsWidgetName, "HeaderText"));
|
||||
},
|
||||
afterpayCopyTokens() {
|
||||
if (this.showAfterpayExtendedPayOption) {
|
||||
return splitCopyOnCMSPlaceHolder(
|
||||
this.getCmsContent(this.cmsWidgetName, "BodyText")
|
||||
);
|
||||
} else {
|
||||
return splitCopyOnCMSPlaceHolder(
|
||||
this.getCmsContent(this.cmsWidgetName, "BodyText2")
|
||||
);
|
||||
}
|
||||
return splitCopyOnCMSPlaceHolder(this.getCmsContent(this.cmsWidgetName, "BodyText"));
|
||||
},
|
||||
modalCopy() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "FooterText");
|
||||
|
|
@ -114,12 +110,6 @@ export default {
|
|||
|
||||
return price;
|
||||
},
|
||||
showAfterpayExtendedPayOption() {
|
||||
return (
|
||||
this.afterpayExtendedPayOptionThreshold &&
|
||||
this.getTierOnePackagePrice >= this.afterpayExtendedPayOptionThreshold
|
||||
);
|
||||
},
|
||||
},
|
||||
components: {},
|
||||
};
|
||||
|
|
@ -138,11 +128,17 @@ export default {
|
|||
flex-direction: row;
|
||||
}
|
||||
|
||||
.callout {
|
||||
font-family:
|
||||
UrbanistSemibold Arial,
|
||||
Helvetica,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
> div:first-of-type {
|
||||
display: flex;
|
||||
color: $black;
|
||||
font-size: $font-size-20;
|
||||
font-weight: $font-weight-600;
|
||||
line-height: 2rem;
|
||||
@include media-breakpoint-down(md) {
|
||||
border-bottom: 1px solid;
|
||||
|
|
@ -170,11 +166,12 @@ export default {
|
|||
}
|
||||
@include media-breakpoint-up(md) {
|
||||
padding-left: 1rem;
|
||||
max-width: 26rem;
|
||||
max-width: 32rem;
|
||||
}
|
||||
}
|
||||
& .alert-heading {
|
||||
font-size: 0.875rem;
|
||||
|
||||
#afterpay-learnmore {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-6 col-xl-4 mt-4">
|
||||
<funnelSubHeader class="mb-5" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<cashOrInsuranceQuestion
|
||||
ref="cashOrInsurance"
|
||||
cmsWidgetName="CashOrInsuranceQuestionWidget"
|
||||
|
|
@ -67,7 +68,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8 col-xl-6">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<contentGroupModal ref="RainDefenseModal" cmsWidgetName="RainDefenseModal" />
|
||||
<contentGroupModal ref="FrontWiperModal" cmsWidgetName="FrontWiperModal" />
|
||||
<contentGroupModal ref="RearWiperModal" cmsWidgetName="RearWiperModal" />
|
||||
|
|
@ -88,8 +89,9 @@
|
|||
|
||||
<textBlock
|
||||
cmsWidgetName="quoteDisclaimer"
|
||||
justifyText="center"
|
||||
typeStyle="caption"
|
||||
class="mb-5 quote-disclaimer text-left text-md-center" />
|
||||
class="mb-5 quote-disclaimer" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -892,31 +894,6 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// Unique to quote page for 2.0/Heritage parity
|
||||
.quote {
|
||||
padding-bottom: 2rem;
|
||||
:deep(.nav-bar) {
|
||||
position: initial;
|
||||
button {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
a.navigation-link {
|
||||
position: absolute;
|
||||
left: 1rem;
|
||||
bottom: 1rem;
|
||||
@include media-breakpoint-up(lg) {
|
||||
left: 6rem;
|
||||
}
|
||||
}
|
||||
.col-auto {
|
||||
width: -webkit-fill-available;
|
||||
}
|
||||
.col-auto {
|
||||
width: -moz-available;
|
||||
}
|
||||
}
|
||||
}
|
||||
.quote-page.show-save-progress-popup .row.justify-content-center {
|
||||
display: none;
|
||||
|
||||
|
|
@ -937,11 +914,6 @@ export default {
|
|||
.text-block {
|
||||
display: block;
|
||||
}
|
||||
:deep(.container .funnel-sub-header) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
:deep(.promo-modal-question a) {
|
||||
@include responsive-font-size-md(0.875rem, 1rem);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,15 @@
|
|||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
|
||||
<div class="return-user small-question-text">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<div class="container page-container-grouped-styles">
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7 return-user-spacing">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center return-user-spacing">
|
||||
<div class="col-md-6 col-xl-4 mt-4">
|
||||
<funnelSubHeader
|
||||
class="justify-content-center d-flex"
|
||||
ref="funnelSubHeader"
|
||||
cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -608,6 +608,7 @@ function setupMocks({ customMountOptions }) {
|
|||
|
||||
wrapper.vm.setCmsContent = jest.fn();
|
||||
wrapper.vm.$refs.datePicker.initializeComponent = jest.fn();
|
||||
wrapper.vm.$refs.datePicker.loadInitialData = jest.fn();
|
||||
wrapper.vm.$refs.locationAlerts.initializeComponent = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
|
||||
|
|
@ -1,10 +1,14 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<div class="container page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<div class="container-fluid page-container-grouped-styles page-schedule">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-4" />
|
||||
<serviceZipModalQuestion
|
||||
editScreenReaderTextCmsWidgetName="ScreenReaderZipEditWidget"
|
||||
|
|
@ -18,6 +22,7 @@
|
|||
@updated-serviceability="setServiceabilityDetails"
|
||||
@updated-contains-military-base="setContainsMilitaryBase"
|
||||
@updated-bill-to-account-number="setBillToAccountNumber"
|
||||
@service-zip-changed-from-modal="onServiceZipChanged"
|
||||
linkWidgetName="ServiceZipLinkWidget"
|
||||
modalWidgetName="ServiceZipModalWidget" />
|
||||
<alert
|
||||
|
|
@ -101,23 +106,12 @@
|
|||
class="ps-4 pe-4 mt-4" />
|
||||
</div>
|
||||
<shopQuestionPopup
|
||||
modalWidgetName="ShopQuestionPopupWidget"
|
||||
:appointmentType="appointmentType"
|
||||
:modelValue="{
|
||||
zipCode: zipCode,
|
||||
state: state,
|
||||
}"
|
||||
:preSelectedProviderNumber="selectedProvider?.providerNumber"
|
||||
:shopProviderData="shopProviderData"
|
||||
:isServiceableInshop="isServiceableInshop"
|
||||
@updated-serviceability="setServiceabilityDetails"
|
||||
v-if="isShopQuestionDisplayed"
|
||||
:selectedProviderNumberFromParent="selectedProvider?.providerNumber"
|
||||
:shopProviderDataFromParent="shopProviderData"
|
||||
@shop-selected="onShopSelected"
|
||||
@update:modelValue="onShopModelUpdated"
|
||||
@updated-zipcode-ctu="setZipcodeCtu"
|
||||
@update-shop-provider-data="shopProviderData = $event"
|
||||
@updated-contains-military-base="setContainsMilitaryBase"
|
||||
:zipCode="zipCode"
|
||||
:isDisplayed="isShopQuestionDisplayed"
|
||||
@updated-serviceability="setServiceabilityDetails"
|
||||
:zipCodeFromParent="zipCode"
|
||||
cmsWidgetName="YourSafeliteShopWidget" />
|
||||
|
||||
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
|
||||
|
|
@ -127,6 +121,8 @@
|
|||
<div class="col-md-6 col-xl-4">
|
||||
<locationAlerts cmsWidgetPrefix="LocationAlert-" ref="locationAlerts" />
|
||||
<datePicker
|
||||
:currentZip="zipCode"
|
||||
:currentProviderNumber="selectedProvider?.providerNumber"
|
||||
v-show="appointmentType"
|
||||
customComponentId="dateQuestion"
|
||||
selectableDatesSetting="custom"
|
||||
|
|
@ -152,7 +148,7 @@
|
|||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
:isForwardActionDisabled="isForwardActionDisabled"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
|
|
@ -170,7 +166,6 @@ import shopQuestion from "@/layouts/service-location/shop-question/shop-question
|
|||
import shopQuestionPopup from "@/layouts/service-location/shop-question/shop-question-popup";
|
||||
import buttonQuestion from "@/digital-components/button-question/button-question.vue";
|
||||
import shopListButton from "@/layouts/service-location/shop-question/shop-list-button/shop-list-button";
|
||||
import { getAvailabilityRating } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
|
||||
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
|
|
@ -258,6 +253,8 @@ const getScheduleApiResponse = async ({
|
|||
endDateString,
|
||||
providerNumber,
|
||||
zipCode,
|
||||
includeMobileTimeSlots = true,
|
||||
includeInshopTimeSlots = true,
|
||||
}) => {
|
||||
const apiEndDateLimit = sumDateString(startDateString, TIME_SLOTS_CALL_DAYS_LIMIT);
|
||||
const difference = calcDaysBetweenDates(startDateString, endDateString);
|
||||
|
|
@ -301,8 +298,8 @@ const getScheduleApiResponse = async ({
|
|||
},
|
||||
};
|
||||
if (apiStartDate < apiEndDate) {
|
||||
storeActionConfigs.push(storeActionConfigInshop);
|
||||
storeActionConfigs.push(storeActionConfigMobile);
|
||||
if (includeInshopTimeSlots) storeActionConfigs.push(storeActionConfigInshop);
|
||||
if (includeMobileTimeSlots) storeActionConfigs.push(storeActionConfigMobile);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -338,6 +335,8 @@ const getScheduleApiResponse = async ({
|
|||
...mobileTimeSlotsData.days,
|
||||
...mobileTimeSlotsResponse.data.days,
|
||||
];
|
||||
mobileTimeSlotsData.type = mobileTimeSlotsResponse.data.type;
|
||||
mobileTimeSlotsData.zipCode = mobileTimeSlotsResponse.data.zipCode;
|
||||
} else {
|
||||
const inshopTimeSlotsResponse =
|
||||
await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
|
|
@ -355,6 +354,9 @@ const getScheduleApiResponse = async ({
|
|||
...inshopTimeSlotsData.days,
|
||||
...inshopTimeSlotsResponse.data.days,
|
||||
];
|
||||
inshopTimeSlotsData.type = inshopTimeSlotsResponse.data.type;
|
||||
inshopTimeSlotsData.providerNumber =
|
||||
inshopTimeSlotsResponse.data.providerNumber;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
|
@ -384,7 +386,7 @@ export default {
|
|||
name: "schedule",
|
||||
data() {
|
||||
return {
|
||||
selectedDate: this.getSelectedDate(),
|
||||
selectedDate: this.getSelectedDateFromStore(),
|
||||
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
|
||||
mobilePremiumAppointmentFee: null,
|
||||
waitListRequested: null,
|
||||
|
|
@ -397,6 +399,7 @@ export default {
|
|||
showPricingByDay: null,
|
||||
selectableDatesInshop: [],
|
||||
selectableDatesMobile: [],
|
||||
preSelectedDate: null,
|
||||
|
||||
streetAddress: this.getServiceAddressFromStore(),
|
||||
apartmentNumberOrBusinessName: this.getServiceAddress2FromStore(),
|
||||
|
|
@ -412,9 +415,9 @@ export default {
|
|||
isRecalibrationServiceableDropoff: null,
|
||||
isGlassServiceableMobile: null,
|
||||
isRecalibrationServiceableMobile: null,
|
||||
appointmentType: this.getAppointmentType(),
|
||||
appointmentTypeFromAppointmentTypeQuestion: this.getAppointmentType(),
|
||||
selectedProvider: this.getSelectedProvider(),
|
||||
appointmentType: this.getAppointmentTypeFromStore(),
|
||||
appointmentTypeFromAppointmentTypeQuestion: this.getAppointmentTypeFromStore(),
|
||||
selectedProvider: this.getSelectedProviderFromStore(),
|
||||
mobileFeePart: null,
|
||||
recycleFeePart: null,
|
||||
zipContainsMilitaryBase: false,
|
||||
|
|
@ -422,8 +425,8 @@ export default {
|
|||
billToAccountNumber: null,
|
||||
shopProviderData: null,
|
||||
navigatingForward: false,
|
||||
isMobileAddressValid: true,
|
||||
shopListButton: shopListButton,
|
||||
lastSelectedInshopOrDropoffProvider: null,
|
||||
};
|
||||
},
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
|
|
@ -467,11 +470,11 @@ export default {
|
|||
let appointmentType = store.getters.order.serviceLocation.appointmentType;
|
||||
|
||||
// Check to see if date should be pre-selected
|
||||
let preSelectedSlot = await store.getters.order.schedule;
|
||||
let scheduleFromStore = await store.getters.order.schedule;
|
||||
let preSelectedDate;
|
||||
|
||||
if (preSelectedSlot.date && preSelectedSlot.date.length > 0) {
|
||||
preSelectedDate = preSelectedSlot.date;
|
||||
if (scheduleFromStore.date && scheduleFromStore.date.length > 0) {
|
||||
preSelectedDate = scheduleFromStore.date;
|
||||
|
||||
if (appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||
preSelectedDate += "-mobile";
|
||||
|
|
@ -480,7 +483,7 @@ export default {
|
|||
// Check to see if pre-selected date should have pricing by day upcharge
|
||||
if (showPricingByDay) {
|
||||
// is this preSelectedDate a higher priced pricingByDay day?
|
||||
const dayIndex = convertDateStringToDate(preSelectedSlot?.date).getDay();
|
||||
const dayIndex = convertDateStringToDate(scheduleFromStore?.date).getDay();
|
||||
const dayObject = DAYS_OF_WEEK[dayIndex];
|
||||
if (dayObject.isPricingByDayUpchargeDay) {
|
||||
includePricingByDayUpcharge = true;
|
||||
|
|
@ -504,18 +507,9 @@ export default {
|
|||
"service-location"
|
||||
);
|
||||
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode, "service-location");
|
||||
const shopProviderData = await getShopProviderData(serviceZipCode); // shopQuestion.methods.loadInitialData(serviceZipCode);
|
||||
const shopProviderData = await getShopProviderData(serviceZipCode);
|
||||
const providerNumber = shopProviderData?.data?.shopProviders[0]?.providerNumber;
|
||||
|
||||
const datePickerInitialDataPromise = await datePicker.methods.loadInitialData({
|
||||
// setup config options for date-picker
|
||||
selectableDatesSetting: "custom",
|
||||
initialViewRowsToShow: NUMBER_OF_CALENDAR_ROWS_TO_SHOW_FOR_INITIAL_VIEW,
|
||||
getSelectableDatesCallback: getScheduleApiResponse,
|
||||
preSelectedDate: preSelectedDate,
|
||||
providerNumber: providerNumber,
|
||||
});
|
||||
|
||||
// Get pricingByDayUpcharge needed for Pricing By Day
|
||||
const pricingByDayUpchargePartPromise = showPricingByDay
|
||||
? getPricingByDayPartWithPrice()
|
||||
|
|
@ -551,10 +545,6 @@ export default {
|
|||
resultKey: "alertReasons",
|
||||
promise: alertReasonsPromise,
|
||||
},
|
||||
{
|
||||
resultKey: "datePickerInitialData",
|
||||
promise: datePickerInitialDataPromise,
|
||||
},
|
||||
{
|
||||
resultKey: "pricingByDayUpchargePart",
|
||||
promise: pricingByDayUpchargePartPromise,
|
||||
|
|
@ -579,6 +569,7 @@ export default {
|
|||
|
||||
const resultMap = await settleAllPromises(promiseResultMap);
|
||||
|
||||
// add pricing by day data
|
||||
const pricingByDayUpcharge =
|
||||
showPricingByDay && resultMap.pricingByDayUpchargePart
|
||||
? await baseMixin.methods.getTotalLineItemPrice(
|
||||
|
|
@ -587,31 +578,21 @@ export default {
|
|||
)
|
||||
: null;
|
||||
|
||||
const datePickerInitialData = resultMap.datePickerInitialData;
|
||||
datePickerInitialData.pricingByDayBasePrice = pricingByDayBasePrice;
|
||||
datePickerInitialData.pricingByDayUpcharge = pricingByDayUpcharge;
|
||||
|
||||
// Call the "next" function to complete the transition to this page.
|
||||
next((vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
vm.$refs.datePicker.initializeComponent(datePickerInitialData);
|
||||
vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons);
|
||||
vm.selectableDatesInshop =
|
||||
datePickerInitialData.initialShopTimeSlotsResponse.inshopTimeSlotsData;
|
||||
vm.selectableDatesMobile =
|
||||
datePickerInitialData.initialShopTimeSlotsResponse.mobileTimeSlotsData;
|
||||
vm.mobilePremiumAppointmentFee = resultMap.premiumFeeWithPrice
|
||||
? resultMap.premiumFeeWithPrice[0]
|
||||
: null;
|
||||
vm.updateFooterButtonText(vm.selectedTimeSlotInfo);
|
||||
vm.setDisplayWaitList();
|
||||
vm.pricingByDayUpchargeLineItem = resultMap.pricingByDayUpchargePart;
|
||||
vm.includePricingByDayUpcharge = includePricingByDayUpcharge;
|
||||
vm.isPricingByDayExperiment = isPricingByDayExperiment;
|
||||
vm.pricingByDayBasePrice = pricingByDayBasePrice;
|
||||
vm.pricingByDayUpcharge = pricingByDayUpcharge;
|
||||
vm.showPricingByDay = showPricingByDay;
|
||||
|
||||
vm.preSelectedDate = preSelectedDate;
|
||||
vm.appointmentType = appointmentType;
|
||||
vm.setData(
|
||||
resultMap.zipCodeData,
|
||||
|
|
@ -619,39 +600,7 @@ export default {
|
|||
resultMap.mobileFeePart,
|
||||
shopProviderData.data
|
||||
);
|
||||
});
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
if (!this.selectedDate) {
|
||||
// if no date is preselected on load, then select the first available
|
||||
if (this.isMobileSelected) {
|
||||
this.selectedDate = returnFirstDate(this.selectableDatesMobile);
|
||||
} else {
|
||||
this.selectedDate = returnFirstDate(this.selectableDatesInshop);
|
||||
}
|
||||
|
||||
// if there is still no selected date, then load more and try again
|
||||
if (!this.selectedDate) {
|
||||
setTimeout(() => {
|
||||
this.$refs.datePicker.showAnotherMonth().then((moreSelectableDates) => {
|
||||
// update global
|
||||
this.selectableDatesInshop.days =
|
||||
moreSelectableDates.inshopTimeSlotsData.days;
|
||||
this.selectableDatesMobile.days =
|
||||
moreSelectableDates.mobileTimeSlotsData.days;
|
||||
|
||||
if (this.isMobileSelected) {
|
||||
this.selectedDate = returnFirstDate(this.selectableDatesMobile);
|
||||
} else {
|
||||
this.selectedDate = returnFirstDate(this.selectableDatesInshop);
|
||||
}
|
||||
|
||||
this.setDisplayWaitList();
|
||||
});
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
vm.initializeDatePicker();
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -664,17 +613,7 @@ export default {
|
|||
};
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue.zipCode !== this.zipCode) {
|
||||
this.resetMobileLocation();
|
||||
this.appointmentType = null;
|
||||
this.selectedProvider = new Provider();
|
||||
}
|
||||
|
||||
this.state = newValue.state;
|
||||
this.zipCode = newValue.zipCode;
|
||||
this.zipCodeCtu = newValue.zipCodeCtu;
|
||||
|
||||
this.$nextTick();
|
||||
this.handleZipCodeChange(newValue);
|
||||
},
|
||||
},
|
||||
isMobileSelected() {
|
||||
|
|
@ -806,20 +745,10 @@ export default {
|
|||
return store.getters.order.policy.isNoComp;
|
||||
},
|
||||
isForwardActionDisabled() {
|
||||
return this.displayNoShopsAlert || !this.isMobileAddressValid;
|
||||
return !this.selectedTimeSlotInfo?.timeSlot?.routeCode || !this.appointmentType;
|
||||
},
|
||||
additionalButtonData() {
|
||||
const startDate = new Date();
|
||||
const endDate = new Date();
|
||||
endDate.setDate(startDate.getDate() + 6);
|
||||
|
||||
const formattedStartDate = startDate.toISOString().split("T")[0];
|
||||
const formattedEndDate = endDate.toISOString().split("T")[0];
|
||||
|
||||
return {
|
||||
availabilityRatingCallback: getAvailabilityRating,
|
||||
startDate: formattedStartDate,
|
||||
endDate: formattedEndDate,
|
||||
shopAppointmentType: this.appointmentType,
|
||||
};
|
||||
},
|
||||
|
|
@ -831,7 +760,11 @@ export default {
|
|||
});
|
||||
};
|
||||
|
||||
if (this.selectedProvider && this.selectedProvider.address) {
|
||||
if (
|
||||
this.selectedProvider &&
|
||||
this.selectedProvider.address &&
|
||||
this.selectedProvider?.providerNumber
|
||||
) {
|
||||
const provider = this.shopProviderData?.shopProviders?.find(
|
||||
(p) => p.providerNumber === this.selectedProvider?.providerNumber
|
||||
);
|
||||
|
|
@ -983,13 +916,13 @@ export default {
|
|||
|
||||
if (shopProviderData) {
|
||||
this.shopProviderData = shopProviderData;
|
||||
this.updateSelectedProvider(); // ensure that page loads with non-null selectedProvider
|
||||
}
|
||||
|
||||
var gaLabel = this.GaLabels.NO;
|
||||
if (this.isServiceableMobile) {
|
||||
gaLabel = this.GaLabels.YES;
|
||||
}
|
||||
|
||||
this.pushEventToGA(
|
||||
this.GaCategories.APPOINTMENT,
|
||||
this.GaActions.MOBILE_AVAILABLE,
|
||||
|
|
@ -1038,10 +971,10 @@ export default {
|
|||
getIsVehicleProtectedFromStore() {
|
||||
return store.getters.order.serviceLocation.isVehicleProtected;
|
||||
},
|
||||
getAppointmentType() {
|
||||
getAppointmentTypeFromStore() {
|
||||
return store.getters.order.serviceLocation.appointmentType;
|
||||
},
|
||||
getSelectedProvider() {
|
||||
getSelectedProviderFromStore() {
|
||||
return store.getters.order.serviceLocation.provider;
|
||||
},
|
||||
resetMobileLocation() {
|
||||
|
|
@ -1051,6 +984,17 @@ export default {
|
|||
|
||||
this.isVehicleProtected = null;
|
||||
},
|
||||
resetWaitlist() {
|
||||
this.dispatchStoreAction(this.storeActions.SAVE_WAITLIST_REQUESTED, false, false);
|
||||
this.waitListRequested = false;
|
||||
},
|
||||
onServiceZipChanged() {
|
||||
this.resetWaitlist();
|
||||
this.appointmentType = null;
|
||||
this.selectedProvider = null;
|
||||
this.appointmentTypeFromAppointmentTypeQuestion = null;
|
||||
this.preSelectedDate = null;
|
||||
},
|
||||
setServiceabilityDetails(serviceabilityDetails) {
|
||||
this.isGlassServiceableInshop = serviceabilityDetails.isGlassServiceableInshop;
|
||||
this.isRecalibrationServiceableInshop =
|
||||
|
|
@ -1164,43 +1108,22 @@ export default {
|
|||
startTime: "08:00",
|
||||
};
|
||||
},
|
||||
onShopSelected(providerObject) {
|
||||
const current = this.shopProviderData?.shopProviders?.find(
|
||||
(p) => p.providerNumber === this.selectedProvider?.providerNumber
|
||||
);
|
||||
if (current) {
|
||||
this.selectedProvider = current;
|
||||
}
|
||||
onShopSelected(shopQuestionPopUpData) {
|
||||
this.resetWaitlist();
|
||||
this.preSelectedDate = null;
|
||||
this.shopProviderData = shopQuestionPopUpData.shopProviderData;
|
||||
const selectedProvider = this.shopProviderData.shopProviders.find((shopProvider) => {
|
||||
return shopProvider.providerNumber === shopQuestionPopUpData.selectedProviderNumber;
|
||||
});
|
||||
|
||||
let provider = this.shopProviderData?.shopProviders?.find(
|
||||
(p) => p.providerNumber === providerObject?.providerNumber
|
||||
);
|
||||
this.selectedProvider = provider;
|
||||
if (providerObject && providerObject.address) {
|
||||
this.zipCode = providerObject.searchedZip;
|
||||
this.state = providerObject.searchedState;
|
||||
}
|
||||
},
|
||||
setZipcodeCtu(zipcodeCtu) {
|
||||
this.zipCodeCtu = zipcodeCtu;
|
||||
},
|
||||
onShopModelUpdated(newModel) {
|
||||
if (newModel.zipCode) {
|
||||
this.zipCode = newModel.zipCode;
|
||||
}
|
||||
if (newModel.zipCodeCtu) {
|
||||
this.zipCodeCtu = newModel.zipCodeCtu;
|
||||
}
|
||||
if (newModel.state) {
|
||||
this.state = newModel.state;
|
||||
}
|
||||
if (this.shopProviderData && newModel.selectedProviderNumber) {
|
||||
const provider = this.shopProviderData.shopProviders.find(
|
||||
(p) => p.providerNumber === newModel.selectedProviderNumber
|
||||
// No zipCodeData comes back if zip was not changed
|
||||
if (shopQuestionPopUpData.zipCodeData) {
|
||||
this.updateSelectedProviderAndZipCode(
|
||||
shopQuestionPopUpData.zipCodeData,
|
||||
selectedProvider
|
||||
);
|
||||
if (provider) {
|
||||
this.selectedProvider = provider;
|
||||
}
|
||||
} else {
|
||||
this.updateSelectedProvider(selectedProvider);
|
||||
}
|
||||
},
|
||||
async getMoreScheduleData(startDate, endDate) {
|
||||
|
|
@ -1208,8 +1131,10 @@ export default {
|
|||
const moreShopTimeSlots = await getScheduleApiResponse({
|
||||
startDateString: startDate,
|
||||
endDateString: endDate,
|
||||
providerNumber: this.selectedProvider.providerNumber,
|
||||
providerNumber: this.selectedProvider?.providerNumber,
|
||||
zipCode: this.zipCode,
|
||||
includeMobileTimeSlots: this.isServiceableMobile,
|
||||
includeInshopTimeSlots: this.isServiceableInshop || this.isServiceableDropoff,
|
||||
});
|
||||
|
||||
// ADD API CALL RESULTS TO EXISTING DATE DATA
|
||||
|
|
@ -1222,11 +1147,71 @@ export default {
|
|||
|
||||
return moreShopTimeSlots;
|
||||
},
|
||||
|
||||
async initializeDatePicker() {
|
||||
this.selectedDate = null;
|
||||
|
||||
const datePickerInitialData = await this.$refs.datePicker.loadInitialData({
|
||||
// setup config options for date-picker
|
||||
selectableDatesSetting: "custom",
|
||||
initialViewRowsToShow: NUMBER_OF_CALENDAR_ROWS_TO_SHOW_FOR_INITIAL_VIEW,
|
||||
getSelectableDatesCallback: getScheduleApiResponse,
|
||||
preSelectedDate: this.preSelectedDate,
|
||||
providerNumber: this.selectedProvider?.providerNumber,
|
||||
zipCode: this.zipCode,
|
||||
includeMobileTimeSlots: this.isServiceableMobile,
|
||||
includeInshopTimeSlots: this.isServiceableInshop || this.isServiceableDropoff,
|
||||
});
|
||||
datePickerInitialData.pricingByDayBasePrice = this.pricingByDayBasePrice;
|
||||
datePickerInitialData.pricingByDayUpcharge = this.pricingByDayUpcharge;
|
||||
await this.$refs.datePicker.initializeComponent(datePickerInitialData);
|
||||
this.selectableDatesInshop =
|
||||
datePickerInitialData.initialShopTimeSlotsResponse.inshopTimeSlotsData;
|
||||
this.selectableDatesMobile =
|
||||
datePickerInitialData.initialShopTimeSlotsResponse.mobileTimeSlotsData;
|
||||
this.setDisplayWaitList();
|
||||
|
||||
if (this.preSelectedDate) this.selectedDate = this.preSelectedDate;
|
||||
|
||||
if (!this.selectedDate) {
|
||||
// if no date is preselected on load, then select the first available
|
||||
let selectedDateMobile = this.getSelectedDateForMobile();
|
||||
let selectedDateInshop = this.getSelectedDateForInshop();
|
||||
|
||||
// if there is still no selected date, then load more and try again
|
||||
if (
|
||||
(this.isServiceableMobile && !selectedDateMobile) ||
|
||||
(this.isServiceableInshop && !selectedDateInshop) ||
|
||||
(this.isServiceableDropoff && !selectedDateInshop)
|
||||
) {
|
||||
await this.$nextTick();
|
||||
await this.$refs.datePicker.showAnotherMonth();
|
||||
|
||||
// update all dates
|
||||
|
||||
// > CHLOE HERD 7/22 -- CASH-1207
|
||||
// > Do not update the available dates again here;
|
||||
// > they have already been updated by `showAnotherMonth`.
|
||||
// > Doing so will likely add or remove dates,
|
||||
// > desyncing the schedule page and the date-picker.
|
||||
|
||||
this.setDisplayWaitList();
|
||||
}
|
||||
|
||||
await this.$nextTick();
|
||||
|
||||
if (this.isMobileSelected) {
|
||||
this.selectedDate = this.getSelectedDateForMobile();
|
||||
} else {
|
||||
this.selectedDate = this.getSelectedDateForInshop();
|
||||
}
|
||||
}
|
||||
},
|
||||
getScheduleApiResponse,
|
||||
getServiceZipCtuCodeFromStore() {
|
||||
return store.getters.order.serviceLocation.zipCodeCtu;
|
||||
},
|
||||
getSelectedDate() {
|
||||
getSelectedDateFromStore() {
|
||||
let isMobileSelected = this.isMobileSelected;
|
||||
if (isMobileSelected === undefined) {
|
||||
isMobileSelected = this.appointmentType === AppointmentTypeStrings.MOBILE;
|
||||
|
|
@ -1270,7 +1255,10 @@ export default {
|
|||
this.appointmentType === AppointmentTypeStrings.DROP_OFF
|
||||
) {
|
||||
if (
|
||||
!timeSlotInfo.timeSlot.routeCode.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)
|
||||
!timeSlotInfo.timeSlot.routeCode.includes(
|
||||
RouteCodeFlags.ALL_DAY_DROP_OFF
|
||||
) &&
|
||||
!timeSlotInfo.timeSlot.routeCode.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)
|
||||
) {
|
||||
navbarButtonText += ` at ${this.getDisplayTextForMilitaryTime(
|
||||
timeSlotInfo.timeSlot.startTime
|
||||
|
|
@ -1328,19 +1316,20 @@ export default {
|
|||
);
|
||||
}
|
||||
},
|
||||
showLoadingModal() {
|
||||
this.$refs.loadingModal.showModal();
|
||||
},
|
||||
async forwardButtonAction() {
|
||||
this.navigatingForward = true;
|
||||
var ctu = this.zipCodeCtu;
|
||||
if (
|
||||
this.appointmentType == AppointmentTypeStrings.MOBILE &&
|
||||
this.selectedProvider &&
|
||||
this.selectedProvider.address
|
||||
) {
|
||||
this.selectedProvider.address.streetAddress = null;
|
||||
this.selectedProvider.address.city = null;
|
||||
this.selectedProvider.address.state = null;
|
||||
this.selectedProvider.address.zipCode = null;
|
||||
this.selectedProvider.address.zipCodeCtu = null;
|
||||
if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||
if (this.selectedProvider && this.selectedProvider.address) {
|
||||
this.selectedProvider.address.streetAddress = null;
|
||||
this.selectedProvider.address.city = null;
|
||||
this.selectedProvider.address.state = null;
|
||||
this.selectedProvider.address.zipCode = null;
|
||||
this.selectedProvider.address.zipCodeCtu = null;
|
||||
}
|
||||
} else {
|
||||
if (this.zipCodeCtu != this.selectedProvider.address.zipCodeCtu) {
|
||||
ctu = this.selectedProvider.address.zipCodeCtu;
|
||||
|
|
@ -1420,12 +1409,15 @@ export default {
|
|||
}
|
||||
|
||||
if (this.selectedTimeSlotInfo.timeSlot.jobMinMinutes == null) {
|
||||
this.selectedTimeSlotInfo.timeSlot.jobMinMinutes = this.isMobileSelected
|
||||
? this.selectableDatesMobile.estimatedServiceMinutesMinimum?.toString()
|
||||
: this.selectableDatesInshop.estimatedServiceMinutesMinimum?.toString();
|
||||
this.selectedTimeSlotInfo.timeSlot.jobMaxMinutes = this.isMobileSelected
|
||||
? this.selectableDatesMobile.estimatedServiceMinutesMaximum?.toString()
|
||||
: this.selectableDatesInshop.estimatedServiceMinutesMaximum?.toString();
|
||||
this.selectedTimeSlotInfo = this.getSelectedTimeSlotInfo();
|
||||
if (this.selectedTimeSlotInfo.timeSlot.jobMinMinutes == null) {
|
||||
this.selectedTimeSlotInfo.timeSlot.jobMinMinutes = this.isMobileSelected
|
||||
? this.selectableDatesMobile.estimatedServiceMinutesMinimum?.toString()
|
||||
: this.selectableDatesInshop.estimatedServiceMinutesMinimum?.toString();
|
||||
this.selectedTimeSlotInfo.timeSlot.jobMaxMinutes = this.isMobileSelected
|
||||
? this.selectableDatesMobile.estimatedServiceMinutesMaximum?.toString()
|
||||
: this.selectableDatesInshop.estimatedServiceMinutesMaximum?.toString();
|
||||
}
|
||||
}
|
||||
|
||||
this.dispatchStoreAction(
|
||||
|
|
@ -1454,19 +1446,19 @@ export default {
|
|||
this.pageName
|
||||
);
|
||||
}
|
||||
this.showLoadingModal();
|
||||
},
|
||||
setDisplayWaitList() {
|
||||
const dateString = this.isMobileSelected
|
||||
? this.selectableDatesMobile?.days[0]?.date
|
||||
: this.selectableDatesInshop?.days[0]?.date;
|
||||
if (
|
||||
experimentMixin.methods.hasSettingEqualTo(
|
||||
experimentSettings.DISPLAY_WAITLIST,
|
||||
"true"
|
||||
) &&
|
||||
this.selectableDatesInshop.days[0] &&
|
||||
this.selectableDatesMobile.days[0]
|
||||
dateString
|
||||
) {
|
||||
const dateString = this.isMobileSelected
|
||||
? this.selectableDatesMobile.days[0].date
|
||||
: this.selectableDatesInshop.days[0].date;
|
||||
const [year, month, day] = dateString.split("-").map(Number);
|
||||
const targetDate = new Date(year, month - 1, day);
|
||||
const currentDate = new Date();
|
||||
|
|
@ -1568,8 +1560,17 @@ export default {
|
|||
},
|
||||
updateTimeSlot(timeSlotObj) {
|
||||
this.selectedTimeSlotInfo = timeSlotObj;
|
||||
if (this.appointmentType !== AppointmentTypeStrings.MOBILE) {
|
||||
if (
|
||||
this.appointmentType !== AppointmentTypeStrings.MOBILE &&
|
||||
this.appointmentType !== null
|
||||
) {
|
||||
// update apptType based on routeCode to determine if it should be dropoff or inshop
|
||||
debugLog(`Updating appointment type based on route code.`);
|
||||
debugLog(`Route code =`, timeSlotObj.timeSlot.routeCode);
|
||||
debugLog(
|
||||
`Parsed type = `,
|
||||
this.getInShopOrDropOffApptType(timeSlotObj.timeSlot.routeCode)
|
||||
);
|
||||
this.appointmentType = this.getInShopOrDropOffApptType(
|
||||
timeSlotObj.timeSlot.routeCode
|
||||
);
|
||||
|
|
@ -1580,56 +1581,128 @@ export default {
|
|||
? AppointmentTypeStrings.DROP_OFF
|
||||
: AppointmentTypeStrings.IN_SHOP;
|
||||
},
|
||||
getSelectedDate() {
|
||||
let dateToSelect;
|
||||
if (this.isMobileSelected) {
|
||||
dateToSelect = returnFirstDate(this.selectableDatesMobile);
|
||||
} else {
|
||||
dateToSelect = returnFirstDate(this.selectableDatesInshop);
|
||||
}
|
||||
if (!dateToSelect) return null;
|
||||
return this.isMobileSelected ? dateToSelect + "-mobile" : dateToSelect;
|
||||
},
|
||||
getSelectedDateForMobile() {
|
||||
let dateToSelect = returnFirstDate(this.selectableDatesMobile);
|
||||
if (!dateToSelect) return null;
|
||||
return dateToSelect + "-mobile";
|
||||
},
|
||||
getSelectedDateForInshop() {
|
||||
let dateToSelect = returnFirstDate(this.selectableDatesInshop);
|
||||
if (!dateToSelect) return null;
|
||||
return dateToSelect;
|
||||
},
|
||||
resetSelectedProvider() {
|
||||
this.selectedProvider = new Provider();
|
||||
this.updateSelectedProvider();
|
||||
},
|
||||
updateSelectedProvider(newProvider) {
|
||||
if (newProvider) {
|
||||
this.selectedProvider = newProvider;
|
||||
this.initializeDatePicker();
|
||||
} else if (this.appointmentType === this.appointmentTypeStrings.MOBILE) {
|
||||
this.selectedProvider = {
|
||||
providerNumber: this.shopProviderData.mobileProviderNumber.toString(),
|
||||
address: {
|
||||
streetAddress: null,
|
||||
city: null,
|
||||
state: null,
|
||||
zipCode: null,
|
||||
zipCodeCtu: null,
|
||||
},
|
||||
};
|
||||
} else if (
|
||||
!this.selectedProvider?.address?.streetAddress &&
|
||||
this.shopProviderData?.shopProviders?.length
|
||||
) {
|
||||
this.selectedProvider = this.shopProviderData.shopProviders[0];
|
||||
}
|
||||
},
|
||||
handleZipCodeChange(newZipCode) {
|
||||
this.resetMobileLocation();
|
||||
|
||||
getShopProviderData(newZipCode.zipCode).then((result) => {
|
||||
this.shopProviderData = result.data;
|
||||
this.resetSelectedProvider();
|
||||
this.state = newZipCode.state;
|
||||
this.zipCode = newZipCode.zipCode;
|
||||
this.zipCodeCtu = newZipCode.zipCodeCtu;
|
||||
this.selectedDate = null;
|
||||
this.initializeDatePicker();
|
||||
});
|
||||
},
|
||||
updateSelectedProviderAndZipCode(newZipCode, newProvider) {
|
||||
this.resetMobileLocation();
|
||||
this.state = newZipCode.state;
|
||||
this.zipCode = newZipCode.zipCode;
|
||||
this.zipCodeCtu = newZipCode.zipCodeCtu;
|
||||
this.zipContainsMilitaryBase = newZipCode.containsMilitaryBase;
|
||||
this.selectedDate = null;
|
||||
this.selectedProvider = newProvider;
|
||||
this.initializeDatePicker();
|
||||
},
|
||||
handleAppointmentTypeChange(newAppointmentType) {
|
||||
this.updateFooterButtonText();
|
||||
if (newAppointmentType === AppointmentTypeStrings.MOBILE) {
|
||||
// Remember last shop selected if previous selection was inshop/dropoff
|
||||
if (
|
||||
(this.appointmentType == AppointmentTypeStrings.IN_SHOP ||
|
||||
AppointmentTypeStrings.DROP_OFF ||
|
||||
AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF) &&
|
||||
this.selectedProvider
|
||||
) {
|
||||
this.lastSelectedInshopOrDropoffProvider = this.selectedProvider;
|
||||
}
|
||||
this.appointmentType = AppointmentTypeStrings.MOBILE;
|
||||
this.updateSelectedProvider();
|
||||
} else if (newAppointmentType) {
|
||||
if (this.appointmentType != AppointmentTypeStrings.MOBILE) {
|
||||
// Clear last shop selected if appointment type was changed in any manner other than from Mobile
|
||||
this.lastSelectedInshopOrDropoffProvider = null;
|
||||
}
|
||||
if (this.selectedTimeSlotInfo?.timeSlot?.routeCode) {
|
||||
// update appointmentType based on routeCode to determine if it should be dropoff or inshop
|
||||
this.appointmentType = this.getInShopOrDropOffApptType(
|
||||
this.selectedTimeSlotInfo.timeSlot.routeCode
|
||||
);
|
||||
} else {
|
||||
this.appointmentType = AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF;
|
||||
}
|
||||
|
||||
// make sure a selectedProvider exists
|
||||
this.updateSelectedProvider(this.lastSelectedInshopOrDropoffProvider);
|
||||
} else {
|
||||
this.appointmentType = null;
|
||||
}
|
||||
this.selectedDate = this.getSelectedDate();
|
||||
this.setDisplayWaitList();
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
zipCode: {
|
||||
handler(newValue) {
|
||||
if (!this.navigatingForward) {
|
||||
getShopProviderData(this.zipCode).then(async (result) => {
|
||||
this.shopProviderData = result.data;
|
||||
if (this.appointmentType === "Mobile") {
|
||||
this.selectedProvider = new Provider(
|
||||
this.shopProviderData.mobileProviderNumber
|
||||
);
|
||||
} else {
|
||||
this.isMobileAddressValid = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
appointmentType: {
|
||||
handler(newValue, oldValue) {
|
||||
if (newValue === AppointmentTypeStrings.MOBILE) {
|
||||
this.selectedProvider = new Provider(
|
||||
this.shopProviderData.mobileProviderNumber
|
||||
);
|
||||
this.isMobileSelected = true;
|
||||
} else {
|
||||
this.selectedProvider = this.shopProviderData.shopProviders[0];
|
||||
}
|
||||
},
|
||||
},
|
||||
appointmentTypeFromAppointmentTypeQuestion: {
|
||||
handler(newValue, oldValue) {
|
||||
if (newValue === AppointmentTypeStrings.MOBILE) {
|
||||
this.appointmentType = AppointmentTypeStrings.MOBILE;
|
||||
} else {
|
||||
if (this.selectedTimeSlotInfo?.timeSlot?.routeCode) {
|
||||
// update appointmentType based on routeCode to determine if it should be dropoff or inshop
|
||||
this.appointmentType = this.getInShopOrDropOffApptType(
|
||||
this.selectedTimeSlotInfo.timeSlot.routeCode
|
||||
);
|
||||
} else {
|
||||
this.appointmentType = AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF;
|
||||
}
|
||||
}
|
||||
this.handleAppointmentTypeChange(newValue);
|
||||
},
|
||||
},
|
||||
|
||||
selectedDate(newValue, oldValue) {
|
||||
// Clear time slot selection if date selected changes
|
||||
if (newValue !== oldValue) {
|
||||
const selectedDate = this.getSelectedDateFromStore();
|
||||
if (oldValue && this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||
oldValue = `${oldValue}-mobile`;
|
||||
}
|
||||
const hasValueChanged = newValue !== oldValue;
|
||||
const isDateDifferent = (newValue || oldValue) !== selectedDate;
|
||||
|
||||
if (hasValueChanged && isDateDifferent) {
|
||||
this.selectedTimeSlotInfo = {
|
||||
timeSlot: {
|
||||
date: null,
|
||||
|
|
|
|||
|
|
@ -94,7 +94,6 @@ export default {
|
|||
border: 1px solid $gray-500;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.2);
|
||||
|
||||
span.premium-appointment-price {
|
||||
position: absolute;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
:answers="answersForDropOffQuestion"
|
||||
isRequired
|
||||
insertOrLabelBeforeFinalOption
|
||||
groupName="chooseDropOffOrInshop">
|
||||
groupName="chooseDropOffOrInshop"
|
||||
validationRules="time-slot-required">
|
||||
<div>
|
||||
<alert
|
||||
v-if="shouldDisplayDropOffAlert"
|
||||
|
|
@ -40,7 +41,7 @@
|
|||
v-if="supplementalInformationBlock"
|
||||
v-html="supplementalInformationBlock" />
|
||||
<div
|
||||
v-if="hasWaitListExperiment && displayWaitListFeature"
|
||||
v-if="hasWaitListExperiment && displayWaitListFeature && selectedDate"
|
||||
class="mt-5 bg-light rounded waitlist">
|
||||
<textBlock
|
||||
cmsWidgetName="WaitListLabelWidget"
|
||||
|
|
@ -53,7 +54,10 @@
|
|||
v-model="waitListRequested"
|
||||
@click="waitListChecked" />
|
||||
</div>
|
||||
<div v-if="waitListRequested" class="rounded waitlist-success" ref="waitlistSuccessMessage">
|
||||
<div
|
||||
v-if="waitListRequested && displayWaitList && selectedDate"
|
||||
class="rounded waitlist-success"
|
||||
ref="waitlistSuccessMessage">
|
||||
<img :src="waitListSuccessImage" class="success-image" />
|
||||
<span v-html="waitListSuccessText" class="success-text"></span>
|
||||
</div>
|
||||
|
|
@ -76,6 +80,7 @@ import alert from "@/ux-components/alert/alert.vue";
|
|||
import experimentMixin from "@/mixins/experiment-mixin";
|
||||
import { experimentSettings } from "@/constants/experiments";
|
||||
import store from "@/store";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
|
||||
// Helpers
|
||||
import { deepClone } from "@/helpers/object-helper";
|
||||
|
|
@ -144,7 +149,6 @@ export default {
|
|||
return {
|
||||
selectedRouteCode: null,
|
||||
timeSlotModalListButton: timeSlotModalListButton,
|
||||
waitListRequested: this.getWaitListRequestedFromStore(),
|
||||
selectedAnswerForDropOffOrInshop: null,
|
||||
selectedAnswerForTimeSlots: null,
|
||||
};
|
||||
|
|
@ -222,7 +226,7 @@ export default {
|
|||
},
|
||||
premiumAppointmentButtonText() {
|
||||
return this.getCmsContent(
|
||||
this.dropOffOrPickATimeQuestionCmsWidgetName,
|
||||
this.mobilePremiumCmsWidgetName,
|
||||
cmsWidgetFieldMappings.TIME_SLOT_BUTTON
|
||||
);
|
||||
},
|
||||
|
|
@ -345,6 +349,14 @@ export default {
|
|||
waitListSuccessText() {
|
||||
return this.getCmsContent("WaitListSuccessWidget", "BodyText");
|
||||
},
|
||||
waitListRequested: {
|
||||
get() {
|
||||
return this.$store.getters.order.customer?.waitListRequested;
|
||||
},
|
||||
set(value) {
|
||||
this.dispatchStoreAction(storeActions.SAVE_WAITLIST_REQUESTED, value);
|
||||
},
|
||||
},
|
||||
isMobileAppointment() {
|
||||
return this.appointmentType === AppointmentTypeStrings.MOBILE;
|
||||
},
|
||||
|
|
@ -432,6 +444,8 @@ export default {
|
|||
);
|
||||
}
|
||||
|
||||
this.resetSelectedTimeSlotIfDateChange(timeSlotsForSelectedDate);
|
||||
|
||||
return availableTimeSlots;
|
||||
},
|
||||
getPremiumAppointmentTimeSlot(timeSlotData) {
|
||||
|
|
@ -468,7 +482,7 @@ export default {
|
|||
return store.getters.order.customer?.waitListRequested;
|
||||
},
|
||||
autoSelectTimeSlotIfOnlyOneIsAvailable() {
|
||||
const numberOfOptions = this.timeSlotsForSelectedDate.timeSlots?.length;
|
||||
const numberOfOptions = this.timeSlotsForSelectedDate?.timeSlots?.length;
|
||||
if (numberOfOptions === 1) {
|
||||
if (this.availableTimeSlots.length > 0) {
|
||||
this.selectedAnswerForTimeSlots = this.availableTimeSlots[0].value;
|
||||
|
|
@ -527,6 +541,23 @@ export default {
|
|||
successMessageElement.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
},
|
||||
resetSelectedTimeSlotIfDateChange(timeSlotsForSelectedDate) {
|
||||
if (this.selectedRouteCode) {
|
||||
const selectedRouteCodeString = this.selectedRouteCode.replace(
|
||||
PREMIUM_TIME_SLOT_ID_FLAG,
|
||||
""
|
||||
);
|
||||
const matchingTimeSlot = timeSlotsForSelectedDate.find((slot) => {
|
||||
return slot.id === selectedRouteCodeString;
|
||||
});
|
||||
if (!matchingTimeSlot) {
|
||||
this.resetSelectedTimeSlot();
|
||||
}
|
||||
}
|
||||
},
|
||||
resetSelectedTimeSlot() {
|
||||
this.selectedRouteCode = null;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
waitListRequested(newVal) {
|
||||
|
|
@ -544,7 +575,9 @@ export default {
|
|||
},
|
||||
selectedDate: {
|
||||
handler() {
|
||||
this.selectedRouteCode = null;
|
||||
this.resetSelectedTimeSlot();
|
||||
this.selectedAnswerForDropOffOrInshop = null;
|
||||
this.selectedAnswerForTimeSlots = null;
|
||||
this.autoSelectTimeSlotIfOnlyOneIsAvailable();
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -73,7 +73,11 @@ export default {
|
|||
},
|
||||
selectedValue: {
|
||||
get: function () {
|
||||
return this.modelValue;
|
||||
if (this.modelValue === AppointmentTypeStrings.DROP_OFF) {
|
||||
return AppointmentTypeStrings.IN_SHOP;
|
||||
} else {
|
||||
return this.modelValue;
|
||||
}
|
||||
},
|
||||
set: function (newValue) {
|
||||
this.$emit("update:modelValue", newValue);
|
||||
|
|
|
|||
|
|
@ -122,35 +122,6 @@ export async function getShopProviderData(serviceZipCode) {
|
|||
);
|
||||
}
|
||||
|
||||
export async function getAvailabilityRating(
|
||||
startDate,
|
||||
endDate,
|
||||
shopAppointmentType,
|
||||
providerNumber
|
||||
) {
|
||||
// For a given shop provider number and date range, get the appointment time slots available
|
||||
const shopTimeSlots = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_SHOP_TIME_SLOTS,
|
||||
{
|
||||
providerNumber: providerNumber,
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
shopAppointmentType: shopAppointmentType,
|
||||
},
|
||||
"service-location",
|
||||
false
|
||||
);
|
||||
|
||||
const numberOfDaysToEvaluate = 2;
|
||||
const isGoodAvailability =
|
||||
shopTimeSlots.data.days.filter((x) => x.timeSlots.length > 0).length >=
|
||||
numberOfDaysToEvaluate;
|
||||
|
||||
const shopStatus = isGoodAvailability ? "high" : "low";
|
||||
|
||||
return shopStatus;
|
||||
}
|
||||
|
||||
export async function getClosestApplicableShops(serviceZipCode, carId, pageNameToLog) {
|
||||
if (!serviceZipCode) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
import {
|
||||
getPricedMobileFeePart,
|
||||
getServiceabilityDetails,
|
||||
getAvailabilityRating,
|
||||
} from "./service-location-helper";
|
||||
import { getPricedMobileFeePart, getServiceabilityDetails } from "./service-location-helper";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
|
||||
jest.mock("@/store", () => ({
|
||||
|
|
@ -320,38 +316,4 @@ describe("service-location-helper.js", () => {
|
|||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAvailabilityRating", () => {
|
||||
it("Should return a 'high' rating", async () => {
|
||||
// Arrange
|
||||
const providerNumber = "0000001";
|
||||
const expected = "high";
|
||||
// Act
|
||||
const result = await getAvailabilityRating(
|
||||
"2023-06-30",
|
||||
"2023-07-06",
|
||||
"Inshop",
|
||||
providerNumber
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it("Should return a 'low' rating", async () => {
|
||||
// Arrange
|
||||
const providerNumber = "0000000";
|
||||
const expected = "low";
|
||||
// Act
|
||||
const result = await getAvailabilityRating(
|
||||
"2023-06-30",
|
||||
"2023-07-06",
|
||||
"Inshop",
|
||||
providerNumber
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<div class="container page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4 mt-4">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<serviceZipModalQuestion
|
||||
|
|
@ -65,8 +69,8 @@
|
|||
alertClass="alert-success" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row appointment-type">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<div class="row justify-content-center appointment-type">
|
||||
<div class="col-md-6">
|
||||
<appointmentTypeQuestion
|
||||
v-model="selectedAppointmentType"
|
||||
v-show="isAppointmentTypeDisplayed"
|
||||
|
|
@ -83,8 +87,21 @@
|
|||
labelBold="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<button-question
|
||||
v-if="selectedAppointmentType == appointmentTypeStrings.IN_SHOP"
|
||||
class="shop-question-button"
|
||||
ref="buttonQuestion"
|
||||
:answers="selectedShopAnswer"
|
||||
:modelValue="selectedProvider?.providerNumber"
|
||||
:isMultiSelect="false"
|
||||
:readonly="true"
|
||||
:questionText="questionText"
|
||||
buttonTypeString="shopListButton"
|
||||
:buttonTypeObject="shopListButton"
|
||||
groupName="chooseShop"
|
||||
textPosition="text-start" />
|
||||
<div class="text-center">
|
||||
<textBlock
|
||||
v-if="mobileFeeApplies && isMobileSelected"
|
||||
|
|
@ -131,10 +148,9 @@
|
|||
import alert from "@/ux-components/alert/alert";
|
||||
import serviceZipModalQuestion from "@/layouts/service-location/service-zip-modal-question/service-zip-modal-question";
|
||||
import appointmentTypeQuestion from "@/layouts/service-location/appointment-type-question/appointment-type-question";
|
||||
import shopQuestion from "@/layouts/service-location/shop-question/shop-question";
|
||||
import shopQuestionPopup from "@/layouts/service-location/shop-question/shop-question-popup";
|
||||
import buttonQuestion from "@/digital-components/button-question/button-question.vue";
|
||||
import shopListButton from "@/layouts/service-location/shop-question/shop-list-button/shop-list-button";
|
||||
import { getAvailabilityRating } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
|
||||
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
|
|
@ -145,7 +161,6 @@ import contentGroupModal from "@/fmg-components/content-group-modal/content-grou
|
|||
import textBlock from "@/digital-components/text-block/text-block";
|
||||
|
||||
// Supporting files
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
import experimentMixin from "@/mixins/experiment-mixin.js";
|
||||
import { experimentSettings } from "@/constants/experiments";
|
||||
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
||||
|
|
@ -432,17 +447,7 @@ export default {
|
|||
return this.displayNoShopsAlert || !this.isMobileAddressValid;
|
||||
},
|
||||
additionalButtonData() {
|
||||
const startDate = new Date();
|
||||
const endDate = new Date();
|
||||
endDate.setDate(startDate.getDate() + 6);
|
||||
|
||||
const formattedStartDate = startDate.toISOString().split("T")[0];
|
||||
const formattedEndDate = endDate.toISOString().split("T")[0];
|
||||
|
||||
return {
|
||||
availabilityRatingCallback: getAvailabilityRating,
|
||||
startDate: formattedStartDate,
|
||||
endDate: formattedEndDate,
|
||||
shopAppointmentType: this.selectedAppointmentType,
|
||||
};
|
||||
},
|
||||
|
|
@ -895,6 +900,7 @@ export default {
|
|||
loadingModal,
|
||||
contentGroupModal,
|
||||
shopQuestionPopup,
|
||||
buttonQuestion,
|
||||
textBlock,
|
||||
},
|
||||
};
|
||||
|
|
@ -907,6 +913,10 @@ export default {
|
|||
@include media-breakpoint-up(md) {
|
||||
padding: 0 0.75rem;
|
||||
}
|
||||
@include media-breakpoint-up(xl) {
|
||||
width: 33.3333333%;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="text-left">
|
||||
<div class="text-center">
|
||||
<div class="update-zip-text-link">
|
||||
<textLink
|
||||
id="serviceZipLinkPromptId"
|
||||
|
|
@ -219,7 +219,7 @@ export default {
|
|||
this.$emit("updated-serviceability", serviceabilityDetails.data);
|
||||
this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase);
|
||||
this.$emit("updated-bill-to-account-number", billToAccountNumber);
|
||||
|
||||
this.$emit("service-zip-changed-from-modal");
|
||||
// update the page level model
|
||||
this.$emit("update:modelValue", this.internalModel);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
cornerStyle="rounded"
|
||||
mask="#####"
|
||||
:includeSearchIcon="includeSearchIcon"
|
||||
@search-icon-click="$emit('search-icon-click')"
|
||||
:displayQuestionText="false"
|
||||
:isRequired="isRequired"
|
||||
:validationRules="computedValidationRules" />
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
<transition name="fade" mode="out-in">
|
||||
<baseInputButton
|
||||
v-bind="$props"
|
||||
buttonWrapperClasses="list-group base-input-button rounded-pill list-button d-flex flex-column w-100 mb-2"
|
||||
buttonWrapperClasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2"
|
||||
v-model="selectedValue">
|
||||
<div
|
||||
:aria-label="buttonLabel"
|
||||
class="button-content list-button-content rounded-pill d-flex flex-column justify-content-center py-3 px-4 p-md-4">
|
||||
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4 p-md-4">
|
||||
<div class="row-one">
|
||||
<span class="m-0 button-label-copy text-truncate" :class="textPosition">
|
||||
{{ buttonLabel }}
|
||||
|
|
@ -58,15 +58,22 @@ export default {
|
|||
beforeMount() {
|
||||
if (this.displayAvailabilityIndicators) {
|
||||
this.displayLoader();
|
||||
const startDate = this.additionalButtonData.startDate;
|
||||
const endDate = this.additionalButtonData.endDate;
|
||||
const startDate = new Date();
|
||||
const endDate = new Date();
|
||||
endDate.setDate(startDate.getDate() + 6);
|
||||
const formattedStartDate = startDate.toISOString().split("T")[0];
|
||||
const formattedEndDate = endDate.toISOString().split("T")[0];
|
||||
|
||||
const shopAppointmentType = this.additionalButtonData.shopAppointmentType;
|
||||
|
||||
this.additionalButtonData
|
||||
.availabilityRatingCallback(startDate, endDate, shopAppointmentType, this.value)
|
||||
.then((data) => {
|
||||
this.availabilityRating = data;
|
||||
});
|
||||
this.getAvailabilityRating(
|
||||
formattedStartDate,
|
||||
formattedEndDate,
|
||||
shopAppointmentType,
|
||||
this.value
|
||||
).then((data) => {
|
||||
this.availabilityRating = data;
|
||||
});
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
@ -103,6 +110,29 @@ export default {
|
|||
displayLoader() {
|
||||
this.isLoaderDisplayed = true;
|
||||
},
|
||||
async getAvailabilityRating(startDate, endDate, shopAppointmentType, providerNumber) {
|
||||
// For a given shop provider number and date range, get the appointment time slots available
|
||||
const shopTimeSlots = await this.dispatchStoreActionWithLogging(
|
||||
this.storeActions.GET_SHOP_TIME_SLOTS,
|
||||
{
|
||||
providerNumber: providerNumber,
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
shopAppointmentType: shopAppointmentType,
|
||||
},
|
||||
"service-location",
|
||||
false
|
||||
);
|
||||
|
||||
const numberOfDaysToEvaluate = 2;
|
||||
const isGoodAvailability =
|
||||
shopTimeSlots.data.days.filter((x) => x.timeSlots.length > 0).length >=
|
||||
numberOfDaysToEvaluate;
|
||||
|
||||
const shopStatus = isGoodAvailability ? "high" : "low";
|
||||
|
||||
return shopStatus;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
loader,
|
||||
|
|
@ -144,10 +174,10 @@ export default {
|
|||
position: relative;
|
||||
background: $white;
|
||||
transition: all 150ms linear;
|
||||
border-radius: $border-radius-lg;
|
||||
border: 1px solid $gray-500;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.2);
|
||||
|
||||
span {
|
||||
&.small {
|
||||
|
|
@ -163,7 +193,6 @@ export default {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1.5rem;
|
||||
justify-content: center;
|
||||
|
||||
.button-label-copy {
|
||||
font-weight: 500;
|
||||
|
|
@ -233,7 +262,7 @@ export default {
|
|||
}
|
||||
|
||||
.row-two {
|
||||
text-align: center;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<transition name="fade" mode="out-in">
|
||||
<div v-if="isDisplayed" class="shop-question" aria-live="polite">
|
||||
<div class="shop-question" aria-live="polite">
|
||||
<div class="modal-link">
|
||||
<text-link
|
||||
linkType="text"
|
||||
|
|
@ -13,20 +13,17 @@
|
|||
<modal
|
||||
ref="shopQuestionModal"
|
||||
:headerText="modalHeaderText"
|
||||
:onModalOpenedCallback="onModalOpened"
|
||||
:onModalClosedCallback="onModalClosed"
|
||||
:onModalOpenedCallback="updateSelectedAnswer"
|
||||
:onModalClosedCallback="resetZipCodeData"
|
||||
:footerButtonText="modalFooterText"
|
||||
:footerButtonDisabled="
|
||||
displayInvalidZipAlert || displayNoShopsAlert || !selectedProviderNumber
|
||||
"
|
||||
:footerButtonDisabled="isModalFooterButtonDisabled"
|
||||
@footer-button-event="setZipCodeAndShop">
|
||||
<form @submit.prevent>
|
||||
<serviceZipQuestion
|
||||
ref="serviceZipQuestion"
|
||||
class="shop-question-zipcode"
|
||||
customInputId="serviceZipCode"
|
||||
v-model="internalModel.zipCode"
|
||||
v-on="{ 'textboxQuestionEvent.inputIdAssigned': onInputIdAssigned }"
|
||||
v-model="localZipCode"
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
@search-icon-click="onSearchZip"
|
||||
@keydown.enter="onSearchZip"
|
||||
|
|
@ -34,43 +31,48 @@
|
|||
:isRequired="false"
|
||||
:validationRules="''" />
|
||||
</form>
|
||||
<alert
|
||||
ref="alertInvalidZip"
|
||||
v-if="displayInvalidZipAlert"
|
||||
class="mb-4"
|
||||
cmsWidgetName="AlertInvalidZipWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
<alert
|
||||
ref="alertNoShops"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertNoShopsWidget"
|
||||
v-if="displayNoShopsAlert"
|
||||
alertClass="alert-warning" />
|
||||
<div v-if="!displayInvalidZipAlert">
|
||||
<buttonQuestion
|
||||
ref="buttonQuestion"
|
||||
buttonTypeString="shopListButton"
|
||||
:buttonTypeObject="shopListButton"
|
||||
class="radioQuestion"
|
||||
:questionText="questionText"
|
||||
:answers="answers"
|
||||
groupName="chooseShop"
|
||||
textPosition="text-start"
|
||||
v-model="selectedProviderNumber"
|
||||
isRequired
|
||||
:validationRules="this.selectedProviderNumber ? '' : 'option-required'" />
|
||||
<div class="show-more-shops-link">
|
||||
<textLink
|
||||
v-if="displaySeeMoreLocationsLink"
|
||||
ref="showMoreShopsLink"
|
||||
id="showMoreShopsId2"
|
||||
cmsWidgetName="ShowMoreShopsLinkWidget"
|
||||
linkType="text"
|
||||
:text="showMoreShopsLinkText"
|
||||
href="#!"
|
||||
@click-event="getNextShopsFromList(3)"
|
||||
:aria-label="showMoreShopsLinkText" />
|
||||
<div v-show="isLoading" class="loader-wrapper">
|
||||
<loader loaderColor="blue" loaderPosition="center" />
|
||||
</div>
|
||||
<div v-show="!isLoading">
|
||||
<alert
|
||||
ref="alertInvalidZip"
|
||||
v-if="displayInvalidZipAlert"
|
||||
class="mb-4"
|
||||
cmsWidgetName="AlertInvalidZipWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
<alert
|
||||
ref="alertNoShops"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertNoShopsWidget"
|
||||
v-if="displayNoShopsAlert"
|
||||
alertClass="alert-warning" />
|
||||
<div v-if="!displayInvalidZipAlert">
|
||||
<buttonQuestion
|
||||
ref="buttonQuestion"
|
||||
buttonTypeString="shopListButton"
|
||||
:buttonTypeObject="shopListButton"
|
||||
class="radioQuestion"
|
||||
:questionText="questionText"
|
||||
:answers="answers"
|
||||
groupName="chooseShop"
|
||||
textPosition="text-start"
|
||||
v-model="localSelectedProviderNumber"
|
||||
isRequired
|
||||
:validationRules="this.localSelectedProviderNumber ? '' : 'option-required'" />
|
||||
<div class="show-more-shops-link">
|
||||
<textLink
|
||||
v-if="displaySeeMoreLocationsLink"
|
||||
ref="showMoreShopsLink"
|
||||
id="showMoreShopsId2"
|
||||
cmsWidgetName="ShowMoreShopsLinkWidget"
|
||||
linkType="text"
|
||||
:text="showMoreShopsLinkText"
|
||||
href="#!"
|
||||
@click-event="updateShopListWithNextShops()"
|
||||
:aria-label="showMoreShopsLinkText" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</modal>
|
||||
|
|
@ -84,81 +86,45 @@ import serviceZipQuestion from "@/layouts/service-location/service-zip-modal-que
|
|||
import buttonQuestion from "@/digital-components/button-question/button-question.vue";
|
||||
import alert from "@/ux-components/alert/alert.vue";
|
||||
import shopListButton from "./shop-list-button/shop-list-button";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
|
||||
// Supporting files
|
||||
import { defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
import { nextTick } from "vue";
|
||||
import { regex } from "@/helpers/validation-rules";
|
||||
import loader from "@/ux-components/loader/loader";
|
||||
|
||||
import { getAvailabilityRating } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
|
||||
|
||||
import { Provider } from "@/layouts/service-location/classes/provider";
|
||||
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
||||
|
||||
import {
|
||||
getPricedMobileFeePart,
|
||||
getPricedRecycleFeePart,
|
||||
getServiceabilityDetails,
|
||||
getBillToAccountNumber,
|
||||
getClosestApplicableShops,
|
||||
getShopProviderData,
|
||||
getZipCodeData,
|
||||
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
|
||||
|
||||
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
|
||||
defineRule("zip-format", regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.SERVICE_ZIP_FORMAT));
|
||||
|
||||
export default {
|
||||
name: "shop-question-popup",
|
||||
mixins: [baseMixin],
|
||||
data() {
|
||||
return {
|
||||
isShopQuestionDisplayed: true,
|
||||
internalModel: this.copyModel(this.modelValue),
|
||||
answers: [],
|
||||
localZipCode: this.zipCodeFromParent,
|
||||
shopListButton: shopListButton,
|
||||
shopIndex: 0,
|
||||
displaySeeMoreLocationsLink: false,
|
||||
serviceZipCodeTextInputId: "",
|
||||
localShopProviderData: this.shopProviderData,
|
||||
selectedProviderNumber: null,
|
||||
isLoadingShops: false,
|
||||
lastSearchedZip: "",
|
||||
lastSuccessfulZip: "",
|
||||
hasLoadedShopData: false,
|
||||
displayInvalidZipAlert: false,
|
||||
areShopsAvialable: true,
|
||||
displayNoShopsAlert: false,
|
||||
numberOfShopsToDisplay: 0,
|
||||
localShopProviderData: this.shopProviderDataFromParent,
|
||||
localSelectedProviderNumber: null,
|
||||
zipCodeData: null,
|
||||
isLoading: false,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
appointmentType: String,
|
||||
shopProviderData: Object,
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
state: "",
|
||||
zipCode: "",
|
||||
}),
|
||||
},
|
||||
isDisplayed: Boolean,
|
||||
modelWidgetName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isServiceableInshop: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
zipCode: {
|
||||
shopProviderDataFromParent: Object,
|
||||
zipCodeFromParent: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
preSelectedProviderNumber: {
|
||||
selectedProviderNumberFromParent: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
|
|
@ -167,14 +133,20 @@ export default {
|
|||
shopQuestionLinkText() {
|
||||
return this.getCmsContent("ChangeMyLocationLinkWidget", "Text");
|
||||
},
|
||||
modalName() {
|
||||
return this.modelWidgetName;
|
||||
},
|
||||
modalHeaderText() {
|
||||
return this.getCmsContent("ShopQuestionWidget", "QuestionText");
|
||||
},
|
||||
selectShopText() {
|
||||
return this.getCmsContent("YourSafeliteShopWidget", "QuestionText");
|
||||
modalFooterText() {
|
||||
return this.getCmsContent("SaveLocationWidget", "Text");
|
||||
},
|
||||
showMoreShopsLinkText() {
|
||||
return this.getCmsContent("ShowMoreShopsLinkWidget", "Text");
|
||||
},
|
||||
displayInvalidZipAlert() {
|
||||
return this.zipCodeData && !this.zipCodeData.isValid;
|
||||
},
|
||||
displayNoShopsAlert() {
|
||||
return !this.shopProviders.length && !this.displayInvalidZipAlert;
|
||||
},
|
||||
modal() {
|
||||
return this.$refs.shopQuestionModal;
|
||||
|
|
@ -182,79 +154,33 @@ export default {
|
|||
shopProviders() {
|
||||
return this.localShopProviderData?.shopProviders ?? [];
|
||||
},
|
||||
modalFooterText() {
|
||||
return this.getCmsContent("SaveLocationWidget", "Text");
|
||||
},
|
||||
selectedValue: {
|
||||
get: function () {
|
||||
return this.modelValue;
|
||||
},
|
||||
set: function (newValue) {},
|
||||
},
|
||||
additionalButtonData() {
|
||||
const startDate = new Date();
|
||||
const endDate = new Date();
|
||||
endDate.setDate(startDate.getDate() + 6);
|
||||
|
||||
const formattedStartDate = startDate.toISOString().split("T")[0];
|
||||
const formattedEndDate = endDate.toISOString().split("T")[0];
|
||||
|
||||
return {
|
||||
availabilityRatingCallback: getAvailabilityRating,
|
||||
startDate: formattedStartDate,
|
||||
endDate: formattedEndDate,
|
||||
shopAppointmentType: this.appointmentType,
|
||||
// Used for availability indicators, could be refactored out of any availability indicator
|
||||
// logic if/when we are sure we'll never have separate drop off/ in shop logic
|
||||
shopAppointmentType: AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF,
|
||||
};
|
||||
},
|
||||
showMoreShopsLinkText() {
|
||||
return this.getCmsContent("ShowMoreShopsLinkWidget", "Text");
|
||||
displaySeeMoreLocationsLink() {
|
||||
if (!this.shopProviders.length) return false;
|
||||
return this.numberOfShopsToDisplay < this.shopProviders.length;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openModal(event) {
|
||||
if (event) event.preventDefault();
|
||||
if (
|
||||
!this.shopProviderData ||
|
||||
!this.shopProviderData.shopProviders ||
|
||||
!this.shopProviderData.shopProviders.length
|
||||
) {
|
||||
// Optionally show a loading message or disable the link
|
||||
return;
|
||||
}
|
||||
this.$refs.shopQuestionModal.openModal();
|
||||
isModalFooterButtonDisabled() {
|
||||
return (
|
||||
this.isLoading ||
|
||||
this.displayInvalidZipAlert ||
|
||||
this.displayNoShopsAlert ||
|
||||
!this.localSelectedProviderNumber
|
||||
);
|
||||
},
|
||||
getSelectedProviderObject(providerNumber) {
|
||||
const provider =
|
||||
this.shopProviders?.find((provider) => provider.providerNumber == providerNumber) ??
|
||||
new Provider();
|
||||
|
||||
return provider;
|
||||
},
|
||||
async getNextShopsFromList(numberToGet = 3) {
|
||||
const logFirstShopsDisplayed = this.shopIndex == 0;
|
||||
|
||||
const shopIterator = (array, n) => {
|
||||
const l = array.length;
|
||||
return () => {
|
||||
const end = this.shopIndex + n;
|
||||
const part = array.slice(this.shopIndex, end);
|
||||
this.shopIndex = end < l ? end : this.shopProviders.length;
|
||||
return part;
|
||||
};
|
||||
};
|
||||
|
||||
const toTitleCase = (str) => {
|
||||
return str.replace(/\w\S*/g, function (txt) {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||
});
|
||||
};
|
||||
|
||||
const nextShop = shopIterator(this.shopProviders, numberToGet);
|
||||
|
||||
// Map API result data
|
||||
const mappedData = nextShop().map((shopProvider) => {
|
||||
const streetAddress = toTitleCase(shopProvider.address.streetAddress);
|
||||
const city = toTitleCase(shopProvider.address.city);
|
||||
answers() {
|
||||
const updatedProvidersToDisplay = this.shopProviders.slice(
|
||||
0,
|
||||
this.numberOfShopsToDisplay
|
||||
);
|
||||
return updatedProvidersToDisplay.map((shopProvider) => {
|
||||
const streetAddress = this.toTitleCase(shopProvider.address.streetAddress);
|
||||
const city = this.toTitleCase(shopProvider.address.city);
|
||||
const state = shopProvider.address.state;
|
||||
const zipCode = shopProvider.address.zipCode;
|
||||
const distanceInMiles = Math.round(shopProvider.distanceInMiles * 2) / 2;
|
||||
|
|
@ -267,354 +193,139 @@ export default {
|
|||
value: shopProvider.providerNumber,
|
||||
};
|
||||
});
|
||||
|
||||
var gaAction = this.GaActions.MORE_LOCATIONS_CLICKED;
|
||||
if (logFirstShopsDisplayed) {
|
||||
gaAction = this.GaActions.SHOPS_FIRST_DISPLAYED;
|
||||
}
|
||||
if (
|
||||
this.appointmentType === AppointmentTypeStrings.IN_SHOP ||
|
||||
this.appointmentType === AppointmentTypeStrings.DROP_OFF ||
|
||||
this.appointmentType === AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF
|
||||
) {
|
||||
var shops = mappedData.map((shop) => {
|
||||
if (shop.value.length > 5 && shop.value.startsWith("00")) {
|
||||
return shop.value.substring(1);
|
||||
} else {
|
||||
return shop.value;
|
||||
}
|
||||
});
|
||||
|
||||
var joinedShops = shops.join(",");
|
||||
if (!joinedShops) {
|
||||
joinedShops = "no-shops";
|
||||
}
|
||||
|
||||
this.pushEventToGA(this.GaCategories.SERVICE_LOCATION, gaAction, joinedShops, true);
|
||||
}
|
||||
|
||||
if (this.answers.length === 0) {
|
||||
this.answers = mappedData;
|
||||
} else {
|
||||
mappedData.forEach((shop) => {
|
||||
this.answers.push(shop);
|
||||
});
|
||||
}
|
||||
|
||||
await nextTick();
|
||||
|
||||
if (this.shopIndex == this.shopProviders.length) {
|
||||
this.displaySeeMoreLocationsLink = false;
|
||||
} else {
|
||||
this.displaySeeMoreLocationsLink = true;
|
||||
}
|
||||
|
||||
await nextTick();
|
||||
|
||||
this.scrollToPageBottom();
|
||||
},
|
||||
async handleUpdate(selectedShopIndex = null) {
|
||||
this.resetAnswers();
|
||||
if (selectedShopIndex >= 3) {
|
||||
await this.getNextShopsFromList(selectedShopIndex + 1);
|
||||
} else {
|
||||
await this.getNextShopsFromList();
|
||||
await nextTick();
|
||||
}
|
||||
},
|
||||
copyModel(modelToCopy) {
|
||||
return {
|
||||
state: modelToCopy.state,
|
||||
zipCode: modelToCopy.zipCode,
|
||||
};
|
||||
},
|
||||
resetAnswers() {
|
||||
this.answers = [];
|
||||
this.shopIndex = 0;
|
||||
},
|
||||
onModalOpened() {
|
||||
const preSelectedIndex = this.shopProviders.findIndex(
|
||||
(provider) => provider.providerNumber == this.preSelectedProviderNumber
|
||||
},
|
||||
methods: {
|
||||
openModal() {
|
||||
// Reset local state from parent
|
||||
this.zipCodeData = null;
|
||||
this.localShopProviderData = this.shopProviderDataFromParent;
|
||||
this.localZipCode = this.zipCodeFromParent;
|
||||
// This will be set once the modal is open (updateSelectedAnswer()) to ensure that the
|
||||
// correct answer is selected
|
||||
this.localSelectedProviderNumber = null;
|
||||
|
||||
const selectedProviderNumberFromParentIndex = this.shopProviders.findIndex(
|
||||
(provider) => provider.providerNumber == this.selectedProviderNumberFromParent
|
||||
);
|
||||
|
||||
if (preSelectedIndex > -1) {
|
||||
this.shopIndex = Math.ceil((preSelectedIndex + 1) / 3) * 3;
|
||||
if (selectedProviderNumberFromParentIndex > -1) {
|
||||
this.numberOfShopsToDisplay =
|
||||
Math.ceil((selectedProviderNumberFromParentIndex + 1) / 3) * 3;
|
||||
} else {
|
||||
this.shopIndex = 3;
|
||||
this.numberOfShopsToDisplay = 3;
|
||||
}
|
||||
this.internalModel = this.copyModel(this.modelValue);
|
||||
this.internalModel.zipCode = this.zipCode;
|
||||
this.localShopProviderData = this.shopProviderData;
|
||||
this.answers = [];
|
||||
this.onSearchZip();
|
||||
this.focusOnZipInput();
|
||||
this.selectedProviderNumber = this.preSelectedProviderNumber;
|
||||
if (
|
||||
(!this.selectedProviderNumber ||
|
||||
!this.shopProviders.some(
|
||||
(p) => p.providerNumber == this.selectedProviderNumber
|
||||
)) &&
|
||||
this.shopProviders.length > 0
|
||||
) {
|
||||
this.selectedProviderNumber = this.shopProviders[0].providerNumber;
|
||||
|
||||
this.updateShopsForZip();
|
||||
this.$refs.shopQuestionModal.openModal();
|
||||
},
|
||||
updateSelectedAnswer() {
|
||||
this.localSelectedProviderNumber = this.selectedProviderNumberFromParent;
|
||||
},
|
||||
toTitleCase(str) {
|
||||
return str.replace(/\w\S*/g, function (txt) {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||
});
|
||||
},
|
||||
updateShopListWithNextShops() {
|
||||
//gaAction = this.GaActions.SHOPS_FIRST_DISPLAYED; --This needs to actually fire on initial display, can never happen here
|
||||
this.numberOfShopsToDisplay += 3;
|
||||
this.pushShopsDisplayedGAEvent(this.answers);
|
||||
},
|
||||
pushShopsDisplayedGAEvent(nextShopListAnswers) {
|
||||
var shops = nextShopListAnswers.map((shop) => {
|
||||
if (shop.value.length > 5 && shop.value.startsWith("00")) {
|
||||
return shop.value.substring(1);
|
||||
} else {
|
||||
return shop.value;
|
||||
}
|
||||
});
|
||||
|
||||
var joinedShops = shops.join(",");
|
||||
if (!joinedShops) {
|
||||
joinedShops = "no-shops";
|
||||
}
|
||||
|
||||
this.pushEventToGA(
|
||||
this.GaCategories.SERVICE_LOCATION,
|
||||
this.GaActions.MORE_LOCATIONS_CLICKED,
|
||||
joinedShops,
|
||||
true
|
||||
);
|
||||
},
|
||||
closeModal() {
|
||||
this.modal.closeModal();
|
||||
},
|
||||
onModalClosed() {
|
||||
this.internalModel = this.copyModel(this.modelValue);
|
||||
this.resetsOnZipInput();
|
||||
},
|
||||
resetsOnZipInput() {
|
||||
this.resetAlerts();
|
||||
},
|
||||
async onSearchZip(event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
if (!this.localZipCode) {
|
||||
this.zipCodeData = {
|
||||
isValid: false,
|
||||
};
|
||||
return;
|
||||
}
|
||||
this.resetsOnZipInput();
|
||||
this.displayNoShopsAlert = false;
|
||||
const zip = this.internalModel.zipCode;
|
||||
const zipCodeData = await this.getZipCodeData(
|
||||
this.internalModel.zipCode,
|
||||
"service-location"
|
||||
);
|
||||
if (!zipCodeData.isValid) {
|
||||
this.displayInvalidZipAlert = true;
|
||||
//this.selectedProviderNumber = null;
|
||||
this.focusOnZipInput();
|
||||
this.resetModalButtonStyle();
|
||||
} else {
|
||||
this.isLoading = true;
|
||||
this.zipCodeData = await this.getZipCodeData(this.localZipCode, "service-location");
|
||||
// Add zipCode to zipCodeData as it does not come back from endpoint
|
||||
this.zipCodeData.zipCode = this.localZipCode;
|
||||
if (this.zipCodeData.isValid) {
|
||||
if (this.isVehicleHeavyTruck) {
|
||||
// check the location endpoint to verify this zip can service a heavy truck
|
||||
const closestShops = await getClosestApplicableShops(
|
||||
this.internalModel.zipCode,
|
||||
this.localZipCode,
|
||||
this.carId,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
if (!closestShops || closestShops.providers?.length === 0) {
|
||||
this.displayNoServiceAlert = true;
|
||||
this.focusOnZipInput();
|
||||
this.resetModalButtonStyle();
|
||||
// We need an alert for heavy trucks
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
getShopProviderData(zip).then(async (result) => {
|
||||
if (this.displayNoShopsAlert === true) {
|
||||
this.displayNoShopsAlert = false;
|
||||
}
|
||||
getShopProviderData(this.localZipCode).then(async (result) => {
|
||||
this.localShopProviderData = result.data;
|
||||
this.updateShopsForZip(this.internalModel.zipCode);
|
||||
if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||
this.selectedProvider = new Provider(
|
||||
this.shopProviderData.mobileProviderNumber
|
||||
);
|
||||
} else {
|
||||
this.selectedProvider = new Provider();
|
||||
}
|
||||
this.numberOfShopsToDisplay = 3;
|
||||
this.updateShopsForZip();
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
async updateShopsForZip(zipCode) {
|
||||
if (!this.appointmentType) {
|
||||
this.answers = [];
|
||||
this.isLoadingShops = false;
|
||||
updateShopsForZip() {
|
||||
if (!this.shopProviders.length) {
|
||||
return;
|
||||
}
|
||||
this.lastSearchedZip = zipCode;
|
||||
if (!zipCode) {
|
||||
this.isLoadingShops = false;
|
||||
this.answers = [];
|
||||
return;
|
||||
}
|
||||
this.isLoadingShops = true;
|
||||
if (!zipCode || !this.shopProviders.length) {
|
||||
this.answers = [];
|
||||
this.displayNoShopsAlert = true;
|
||||
this.displaySeeMoreLocationsLink = false;
|
||||
this.isLoadingShops = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedIndex = this.shopProviders.findIndex(
|
||||
(provider) => provider.providerNumber == this.selectedProviderNumber
|
||||
);
|
||||
|
||||
// If not found, only show the first 3 shops
|
||||
if (selectedIndex === -1) {
|
||||
this.shopIndex = 3;
|
||||
}
|
||||
|
||||
const sortedShops = this.getShopsByZip(zipCode, this.shopProviders, this.shopIndex);
|
||||
|
||||
const toTitleCase = (str) => {
|
||||
return str.replace(/\w\S*/g, function (txt) {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||
});
|
||||
};
|
||||
|
||||
const mappedData = sortedShops.map((shopProvider) => {
|
||||
const streetAddress = toTitleCase(shopProvider.address.streetAddress);
|
||||
const city = toTitleCase(shopProvider.address.city);
|
||||
const state = shopProvider.address.state;
|
||||
const zipCode = shopProvider.address.zipCode;
|
||||
const distanceInMiles = Math.round(shopProvider.distanceInMiles * 2) / 2;
|
||||
|
||||
const startDate = new Date();
|
||||
const endDate = new Date();
|
||||
endDate.setDate(startDate.getDate() + 6);
|
||||
|
||||
const formattedStartDate = startDate.toISOString().split("T")[0];
|
||||
const formattedEndDate = endDate.toISOString().split("T")[0];
|
||||
|
||||
return {
|
||||
buttonLabel: city,
|
||||
buttonLabelSubCopy: `${distanceInMiles} mi`,
|
||||
buttonBodyCopy: `${streetAddress}, ${city}, ${state} ${zipCode}`,
|
||||
additionalButtonData: {
|
||||
availabilityRatingCallback: getAvailabilityRating,
|
||||
startDate: formattedStartDate,
|
||||
endDate: formattedEndDate,
|
||||
shopAppointmentType: this.appointmentType,
|
||||
},
|
||||
value: shopProvider.providerNumber,
|
||||
};
|
||||
});
|
||||
this.answers = mappedData;
|
||||
if (
|
||||
this.selectedProviderNumber &&
|
||||
!this.answers.some((a) => String(a.value) === String(this.selectedProviderNumber))
|
||||
!this.answers.some(
|
||||
(a) => String(a.value) === String(this.localSelectedProviderNumber)
|
||||
)
|
||||
) {
|
||||
this.selectedProviderNumber = null;
|
||||
this.localSelectedProviderNumber = null;
|
||||
}
|
||||
this.shopIndex = mappedData.length;
|
||||
this.displaySeeMoreLocationsLink = this.shopIndex < this.shopProviders.length;
|
||||
if (mappedData.length > 0) {
|
||||
this.lastSuccessfulZip = zipCode;
|
||||
}
|
||||
this.isLoadingShops = false;
|
||||
this.displayNoShopsAlert = this.answers.length === 0;
|
||||
},
|
||||
resetModalButtonStyle() {
|
||||
this.modal.resetButtonStyle();
|
||||
},
|
||||
onInputIdAssigned(inputId) {
|
||||
this.serviceZipCodeTextInputId = inputId;
|
||||
},
|
||||
focusOnZipInput() {
|
||||
const input = document.getElementById(this.serviceZipCodeTextInputId);
|
||||
input?.focus();
|
||||
},
|
||||
resetAlerts() {
|
||||
this.displayInvalidZipAlert = false;
|
||||
this.displayNoServiceAlert = false;
|
||||
this.displayNoShopsAlert = false;
|
||||
// I think we should call our new pushGA Event here with new mapped data
|
||||
},
|
||||
async setZipCodeAndShop() {
|
||||
if (
|
||||
this.displayInvalidZipAlert ||
|
||||
this.displayNoShopsAlert ||
|
||||
!this.selectedProviderNumber
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const zipToEmit = this.lastSearchedZip;
|
||||
|
||||
const selectedProvider = this.shopProviders.find(
|
||||
(provider) => provider.providerNumber == this.selectedProviderNumber
|
||||
);
|
||||
this.resetAlerts();
|
||||
|
||||
const zipCodeData = await this.getZipCodeData(zipToEmit, "service-location");
|
||||
|
||||
if (!zipCodeData.isValid) {
|
||||
this.displayInvalidZipAlert = true;
|
||||
this.focusOnZipInput();
|
||||
this.resetModalButtonStyle();
|
||||
} else {
|
||||
if (this.isVehicleHeavyTruck) {
|
||||
// check the location endpoint to verify this zip can service a heavy truck
|
||||
const closestShops = await getClosestApplicableShops(
|
||||
selectedProvider.address.zipCode,
|
||||
this.carId,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
if (!closestShops || closestShops.providers?.length === 0) {
|
||||
this.displayNoServiceAlert = true;
|
||||
this.focusOnZipInput();
|
||||
this.resetModalButtonStyle();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
selectedProvider.address.zipCodeCtu = zipCodeData.zipCodeCtu;
|
||||
|
||||
// retrieve mobile fee part
|
||||
const serviceZipCode = selectedProvider.address.zipCode;
|
||||
const mobileFeePart = await getPricedMobileFeePart(
|
||||
serviceZipCode,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
// retrieve recycle fee part
|
||||
const recycleFeePart = await getPricedRecycleFeePart(
|
||||
serviceZipCode,
|
||||
"service-location"
|
||||
);
|
||||
const shopQuestionPopUpData = {
|
||||
zipCodeData: this.zipCodeData,
|
||||
shopProviderData: this.localShopProviderData,
|
||||
selectedProviderNumber: this.localSelectedProviderNumber,
|
||||
};
|
||||
this.$emit("shop-selected", shopQuestionPopUpData);
|
||||
|
||||
if (this.zipCodeData) {
|
||||
// retrieve serviceability details
|
||||
const serviceabilityDetails = await getServiceabilityDetails(
|
||||
serviceZipCode,
|
||||
this.zipCodeData.zipCode,
|
||||
null,
|
||||
"service-location"
|
||||
this.pageName
|
||||
);
|
||||
|
||||
const billToAccountNumber = await getBillToAccountNumber(
|
||||
selectedProvider.address.zipCodeCtu
|
||||
);
|
||||
|
||||
// update content related to service zip code
|
||||
this.$emit("updated-mobile-fee-part", mobileFeePart);
|
||||
this.$emit("updated-recycle-fee-part", recycleFeePart);
|
||||
this.$emit("updated-serviceability", serviceabilityDetails.data);
|
||||
this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase);
|
||||
this.$emit("updated-bill-to-account-number", billToAccountNumber);
|
||||
this.$emit("updated-zipcode-ctu", selectedProvider.address.zipCodeCtu);
|
||||
|
||||
this.$emit("update:modelValue", this.selectedValue);
|
||||
this.$emit("update-shop-provider-data", this.localShopProviderData);
|
||||
this.internalModel.state = zipCodeData.state;
|
||||
this.$emit("shop-selected", {
|
||||
providerNumber: selectedProvider?.providerNumber,
|
||||
address: selectedProvider?.address,
|
||||
searchedZip: zipToEmit,
|
||||
searchedState: this.internalModel.state,
|
||||
});
|
||||
|
||||
this.closeModal();
|
||||
}
|
||||
},
|
||||
getShopsByZip(zipCode, shops, numberToGet = 3) {
|
||||
return shops
|
||||
.filter(
|
||||
(shop) => shop.address && shop.address.zipCode && shop.address.zipCode !== ""
|
||||
)
|
||||
.sort((a, b) => a.distanceInMiles - b.distanceInMiles)
|
||||
.slice(0, numberToGet);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
shopProviderData: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
this.localShopProviderData = newVal;
|
||||
this.hasLoadedShopData = true;
|
||||
},
|
||||
|
||||
this.closeModal();
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
|
@ -623,6 +334,7 @@ export default {
|
|||
serviceZipQuestion,
|
||||
buttonQuestion,
|
||||
alert,
|
||||
loader,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -750,4 +462,10 @@ export default {
|
|||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
.loader-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
:questionText="questionText"
|
||||
:answers="answers"
|
||||
groupName="chooseShop"
|
||||
textPosition="text-center"
|
||||
textPosition="text-start"
|
||||
v-model="selectedProviderNumber"
|
||||
isRequired
|
||||
validationRules="option-required" />
|
||||
|
|
@ -49,8 +49,6 @@ import { errorMessages } from "@/constants/error-messages";
|
|||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
import { nextTick } from "vue";
|
||||
|
||||
import { getAvailabilityRating } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
|
||||
|
||||
import { Provider } from "@/layouts/service-location/classes/provider";
|
||||
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
||||
|
||||
|
|
@ -109,17 +107,7 @@ export default {
|
|||
return this.getCmsContent("ShowMoreShopsLinkWidget", "Text");
|
||||
},
|
||||
additionalButtonData() {
|
||||
const startDate = new Date();
|
||||
const endDate = new Date();
|
||||
endDate.setDate(startDate.getDate() + 6);
|
||||
|
||||
const formattedStartDate = startDate.toISOString().split("T")[0];
|
||||
const formattedEndDate = endDate.toISOString().split("T")[0];
|
||||
|
||||
return {
|
||||
availabilityRatingCallback: getAvailabilityRating,
|
||||
startDate: formattedStartDate,
|
||||
endDate: formattedEndDate,
|
||||
shopAppointmentType: this.selectedAppointmentType,
|
||||
};
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4 mt-4">
|
||||
<funnelSubHeader class="pb-4" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div>
|
||||
<textboxQuestion
|
||||
|
|
@ -355,6 +359,9 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.vinlookupquestion p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.vinlookupquestion strong {
|
||||
font-weight: 500;
|
||||
color: $black;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mb-7" />
|
||||
<div class="container-fluid page-container-grouped-styles vehicle-damage">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4 mt-4">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<alert
|
||||
ref="vehicleChangeAlert"
|
||||
v-if="shouldDisplayVehicleChangeAlert"
|
||||
|
|
@ -14,7 +18,7 @@
|
|||
:isDismissible="false" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<damageLocationQuestion
|
||||
ref="damageLocation"
|
||||
|
|
@ -23,8 +27,8 @@
|
|||
groupName="DamageLocationQuestion" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<windshieldOptions
|
||||
class="windshield-options"
|
||||
ref="windshieldOptions"
|
||||
|
|
@ -57,8 +61,8 @@
|
|||
validationRules="replace-options-required" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!isContinueEnabled"
|
||||
|
|
|
|||
|
|
@ -2,18 +2,30 @@
|
|||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
|
||||
<div class="vehicle-parts small-question-text">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<div class="container page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<funnelSubHeader
|
||||
ref="funnelSubHeader"
|
||||
cmsWidgetName="FunnelSubHeaderWidget"
|
||||
class="mb-7" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4 mt-4">
|
||||
<funnelSubHeader
|
||||
ref="funnelSubHeader"
|
||||
cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4 my-5">
|
||||
<alert
|
||||
class="rounded border-0 shadow-sm"
|
||||
alertClass="alert-warning"
|
||||
cmsWidgetName="AlertWidget"
|
||||
:isDismissible="false" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<div v-for="(item, i) in PartsOrQuestions" :key="i">
|
||||
<!-- Render horizontal lines if there is multi-glass (aka if i > 0) -->
|
||||
<hr v-if="i > 0" />
|
||||
|
|
@ -51,6 +63,7 @@ import glassPartQuestion from "@/layouts/vehicle-parts/glass-part-question/glass
|
|||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question";
|
||||
// Supporting Files
|
||||
|
|
@ -231,6 +244,7 @@ export default {
|
|||
funnelHeader,
|
||||
funnelSubHeader,
|
||||
navbar,
|
||||
alert,
|
||||
loadingModal,
|
||||
saveProgressModalQuestion,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="siteSubHeader" />
|
||||
<vehicleQuestion
|
||||
ref="vehicleYearQuestion"
|
||||
|
|
|
|||
|
|
@ -63,12 +63,12 @@ export default {
|
|||
&:after {
|
||||
content: "";
|
||||
transition: all 0.5s ease;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24'%3E%3Cpath fill='%230070D1' d='M12 0a12 12 0 1 0 0 24 12 12 0 0 0 0-24Zm6.113 11.28-5.52 5.52a.84.84 0 0 1-1.186 0l-5.52-5.52a.843.843 0 1 1 1.186-1.2L12 15.012l4.927-4.932a.843.843 0 1 1 1.186 1.2Z'/%3E%3C/svg%3E");
|
||||
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: 24px;
|
||||
height: 24px;
|
||||
width: 16px;
|
||||
height: 9px;
|
||||
display: inline-flex;
|
||||
}
|
||||
&.active:after {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<div class="container page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4 mt-4">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<textboxQuestion
|
||||
class="mb-2 mt-5"
|
||||
|
|
|
|||
|
|
@ -202,21 +202,21 @@ html {
|
|||
|
||||
.form-test-invalid {
|
||||
&.btn.btn-primary {
|
||||
color: $white;
|
||||
background: $red;
|
||||
color: $gray-600;
|
||||
background: $gray-200;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
font-weight: $font-weight-normal;
|
||||
}
|
||||
&.btn.btn-primary:hover,
|
||||
&.btn.btn-primary:focus {
|
||||
background: $red;
|
||||
background: $gray-200;
|
||||
box-shadow: none;
|
||||
}
|
||||
&.btn.btn-primary:focus-visible {
|
||||
box-shadow:
|
||||
0 0 0 3px $white,
|
||||
0 0 0 5.5px $red;
|
||||
0 0 0 5.5px $gray-700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,13 @@ body {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100dvh;
|
||||
}
|
||||
.container {
|
||||
@include media-breakpoint-up(xl) {
|
||||
max-width: 1020px;
|
||||
form {
|
||||
> .container-fluid {
|
||||
margin-bottom: -1rem;
|
||||
@media screen and (min-width: 1090px) {
|
||||
margin-bottom: -5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.container-fluid {
|
||||
|
|
@ -87,7 +90,7 @@ body {
|
|||
}
|
||||
}
|
||||
.modal-open:not(.prevent-modal-scroll) {
|
||||
.container {
|
||||
.container-fluid {
|
||||
&.page-container-grouped-styles {
|
||||
overflow: hidden;
|
||||
height: auto;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,3 @@
|
|||
.base-input-button {
|
||||
&.rounded-pill {
|
||||
&:not(.has-error):hover {
|
||||
cursor: pointer;
|
||||
|
||||
&.list-button,
|
||||
&.list-button-horizontal,
|
||||
&.list-card {
|
||||
&:not(.selected) {
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
@include box-shadow-hover($blue-300);
|
||||
border-radius: 50rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.base-input-button {
|
||||
&:not(.has-error):hover {
|
||||
cursor: pointer;
|
||||
|
|
|
|||
|
|
@ -204,7 +204,6 @@ $enable-important-utilities: false;
|
|||
$grid-breakpoints: (
|
||||
xs: 0,
|
||||
md: 768px,
|
||||
lg: 992px,
|
||||
xl: 1200px,
|
||||
xxl: 1440px,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<template>
|
||||
<button
|
||||
:aria-disabled="isDisabled"
|
||||
class="btn rounded-pill d-flex align-items-center justify-content-center py-3 px-6 delay"
|
||||
class="btn d-flex align-items-center justify-content-center py-3 px-4 delay"
|
||||
:class="[
|
||||
isPrimary ? 'btn-primary' : 'btn-secondary',
|
||||
isFloat ? 'float-end' : '',
|
||||
isLoaderDisplayed && !suppressLoader ? 'has-loader' : '',
|
||||
]"
|
||||
@click="clicked">
|
||||
<span class="m-0" v-html="buttonText"></span>
|
||||
<span class="m-0">{{ this.buttonText }}</span>
|
||||
<loader
|
||||
class="ms-2"
|
||||
v-if="isLoaderDisplayed && !suppressLoader"
|
||||
|
|
@ -63,23 +63,21 @@ export default {
|
|||
|
||||
<style lang="scss">
|
||||
.btn {
|
||||
&:first-child:active {
|
||||
background-color: $red;
|
||||
}
|
||||
&.btn-primary {
|
||||
position: relative;
|
||||
background: $red;
|
||||
background: linear-gradient(270deg, $blue 0%, $blue-800 100%);
|
||||
border: none;
|
||||
border-radius: $border-radius-lg;
|
||||
color: $white;
|
||||
justify-content: center;
|
||||
font-weight: 500;
|
||||
@media (hover: hover) {
|
||||
background: $red;
|
||||
background: linear-gradient(270deg, $blue 0%, $blue-800 100%);
|
||||
}
|
||||
&:focus {
|
||||
box-shadow:
|
||||
0 0 0 3px,
|
||||
0 0 0 5.5px $red;
|
||||
0 0 0 5.5px $blue-700;
|
||||
}
|
||||
&:focus, // Mouse, touch, stylus focus
|
||||
&:focus-visible {
|
||||
|
|
@ -87,26 +85,27 @@ export default {
|
|||
outline: none;
|
||||
box-shadow:
|
||||
0 0 0 3px,
|
||||
0 0 0 5.5px $red;
|
||||
0 0 0 5.5px $blue-700;
|
||||
color: $white;
|
||||
background: $red;
|
||||
background: linear-gradient(270deg, rgba(6, 87, 124, 1) 0%, rgba(6, 87, 124, 1) 100%);
|
||||
}
|
||||
&:disabled {
|
||||
background: $gray-200 !important;
|
||||
background: $red !important;
|
||||
color: $white !important;
|
||||
background: linear-gradient(270deg, $gray-200 0%, $gray-200 100%) !important;
|
||||
color: $gray-600 !important;
|
||||
font-weight: 400;
|
||||
height: 48px;
|
||||
border: none;
|
||||
border-radius: $border-radius-lg;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
&.has-loader {
|
||||
color: $white;
|
||||
background: $red;
|
||||
background: $blue-700;
|
||||
box-shadow:
|
||||
0 0 0 3px,
|
||||
0 0 0 5.5px $red;
|
||||
0 0 0 5.5px $blue-700;
|
||||
pointer-events: auto;
|
||||
}
|
||||
&.delay {
|
||||
|
|
@ -117,13 +116,15 @@ export default {
|
|||
&.btn-secondary {
|
||||
position: relative;
|
||||
background: transparent;
|
||||
border: 1px solid $red;
|
||||
color: $red;
|
||||
border: 1px solid $blue;
|
||||
border-radius: $border-radius-lg;
|
||||
color: $blue;
|
||||
font-weight: 500;
|
||||
transition: all 150ms linear;
|
||||
height: 3rem;
|
||||
&:hover {
|
||||
color: $white;
|
||||
@include blue-gradient;
|
||||
}
|
||||
&:focus, // Mouse, touch, stylus focus
|
||||
&:focus-visible {
|
||||
|
|
@ -131,20 +132,23 @@ export default {
|
|||
outline: none;
|
||||
box-shadow:
|
||||
0 0 0 3px $white,
|
||||
0 0 0 5.5px $red;
|
||||
0 0 0 5.5px $blue-700;
|
||||
color: $white;
|
||||
@include blue-gradient;
|
||||
}
|
||||
&:disabled {
|
||||
background: transparent;
|
||||
color: $red !important;
|
||||
color: $gray-550 !important;
|
||||
font-weight: 400;
|
||||
height: 48px;
|
||||
border: 1px solid $red;
|
||||
border: 1px solid $gray-550;
|
||||
border-radius: $border-radius-lg;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
&.has-loader {
|
||||
color: $white;
|
||||
@include blue-gradient;
|
||||
pointer-events: none;
|
||||
}
|
||||
&.delay {
|
||||
|
|
@ -156,6 +160,7 @@ export default {
|
|||
position: relative;
|
||||
background: $green-100;
|
||||
border: 1px solid $green-400;
|
||||
border-radius: $border-radius-lg;
|
||||
color: $black;
|
||||
font-weight: 500;
|
||||
transition: all 150ms linear;
|
||||
|
|
@ -180,11 +185,13 @@ export default {
|
|||
font-weight: 400;
|
||||
height: 48px;
|
||||
border: 1px solid $gray-550;
|
||||
border-radius: $border-radius-lg;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
&.has-loader {
|
||||
color: $white;
|
||||
@include blue-gradient;
|
||||
pointer-events: none;
|
||||
}
|
||||
&.delay {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ export default {
|
|||
|
||||
<style lang="scss">
|
||||
.list-button-horizontal {
|
||||
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.2);
|
||||
input[type="radio"],
|
||||
input[type="checkbox"] {
|
||||
position: absolute;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<baseInputButton
|
||||
v-bind="$props"
|
||||
buttonWrapperClasses="list-group base-input-button list-button rounded-pill d-flex flex-column w-100 mb-2"
|
||||
buttonWrapperClasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2"
|
||||
v-model="selectedValue">
|
||||
<div
|
||||
:aria-label="buttonLabel"
|
||||
|
|
@ -102,11 +102,10 @@ export default {
|
|||
position: relative;
|
||||
background: $white;
|
||||
transition: all 150ms linear;
|
||||
border-radius: $border-radius-lg;
|
||||
border: 1px solid $gray-500;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
border-radius: 50rem;
|
||||
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.2);
|
||||
|
||||
span {
|
||||
&.small {
|
||||
|
|
|
|||
|
|
@ -76,7 +76,6 @@ export default {
|
|||
}
|
||||
.list-card {
|
||||
border: 1px solid $gray-500;
|
||||
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.2);
|
||||
|
||||
&.has-error {
|
||||
input[type="checkbox"],
|
||||
|
|
|
|||
|
|
@ -64,9 +64,10 @@ export default {
|
|||
<style lang="scss">
|
||||
a {
|
||||
color: $blue;
|
||||
text-decoration: none;
|
||||
text-underline-offset: 4px; //Per Devyn. This can't be documented in Figma so there is a comment with the Prototype mocks on the Quote page in Figma
|
||||
line-height: 2;
|
||||
padding: 0 0 4px 0;
|
||||
font-weight: 500;
|
||||
max-width: fit-content;
|
||||
&:hover {
|
||||
color: $blue-700;
|
||||
|
|
@ -80,13 +81,10 @@ a {
|
|||
&.navigation-link,
|
||||
&.new-window-link {
|
||||
font-size: 0.875rem;
|
||||
color: $blue;
|
||||
color: $black;
|
||||
line-height: 26px;
|
||||
white-space: nowrap;
|
||||
align-items: center;
|
||||
font-family: UrbanistSemibold;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
@include media-breakpoint-up(md) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue