diff --git a/src/constants/progress-bar-mapper.js b/src/constants/progress-bar-mapper.js
index 98ae07992..dc1bc4a05 100644
--- a/src/constants/progress-bar-mapper.js
+++ b/src/constants/progress-bar-mapper.js
@@ -15,6 +15,7 @@ const pagePercentageMapper = {
"insurance-company": 52,
"service-location": 60,
schedule: 72,
+ "mobile-details": 76,
"customer-details": 84,
"payment-method": 92,
payment: 96,
diff --git a/src/constants/schedule-constants.js b/src/constants/schedule-constants.js
index fbd165ad1..8ac97b5de 100644
--- a/src/constants/schedule-constants.js
+++ b/src/constants/schedule-constants.js
@@ -2,6 +2,7 @@ const AppointmentTypeStrings = {
IN_SHOP: "Inshop",
MOBILE: "Mobile",
DROP_OFF: "Dropoff",
+ IN_SHOP_OR_DROP_OFF: "InshopOrDropoff",
};
const PREMIUM_TIME_SLOT_ID_FLAG = "-PREMIUM";
diff --git a/src/constants/state-options.js b/src/constants/state-options.js
new file mode 100644
index 000000000..19eb348d5
--- /dev/null
+++ b/src/constants/state-options.js
@@ -0,0 +1,54 @@
+const stateOptions = {
+ AL: "Alabama",
+ AK: "Alaska",
+ AZ: "Arizona",
+ AR: "Arkansas",
+ CA: "California",
+ CO: "Colorado",
+ CT: "Connecticut",
+ DE: "Delaware",
+ DC: "District Of Columbia",
+ FL: "Florida",
+ GA: "Georgia",
+ HI: "Hawaii",
+ ID: "Idaho",
+ IL: "Illinois",
+ IN: "Indiana",
+ IA: "Iowa",
+ KS: "Kansas",
+ KY: "Kentucky",
+ LA: "Louisiana",
+ ME: "Maine",
+ MD: "Maryland",
+ MA: "Massachusetts",
+ MI: "Michigan",
+ MN: "Minnesota",
+ MS: "Mississippi",
+ MO: "Missouri",
+ MT: "Montana",
+ NE: "Nebraska",
+ NV: "Nevada",
+ NH: "New Hampshire",
+ NJ: "New Jersey",
+ NM: "New Mexico",
+ NY: "New York",
+ NC: "North Carolina",
+ ND: "North Dakota",
+ OH: "Ohio",
+ OK: "Oklahoma",
+ OR: "Oregon",
+ PA: "Pennsylvania",
+ RI: "Rhode Island",
+ SC: "South Carolina",
+ SD: "South Dakota",
+ TN: "Tennessee",
+ TX: "Texas",
+ UT: "Utah",
+ VT: "Vermont",
+ VA: "Virginia",
+ WA: "Washington",
+ WV: "West Virginia",
+ WI: "Wisconsin",
+ WY: "Wyoming",
+};
+export { stateOptions };
diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js
index cb2c84940..0fc1cc1b0 100644
--- a/src/constants/store-actions.js
+++ b/src/constants/store-actions.js
@@ -79,6 +79,7 @@ const storeActions = {
SAVE_VIN_LOOKUP: "saveVinLookup",
SAVE_SERVICE_ZIP_CODE_INFO: "saveServiceZipCodeInfo",
SAVE_SERVICE_LOCATION: "saveServiceLocation",
+ SAVE_MOBILE_DETAILS: "saveMobileDetails",
SAVE_SCHEDULE: "saveSchedule",
SAVE_EMAIL: "saveEmail",
SAVE_PHONE_NUMBER: "savePhoneNumber",
diff --git a/src/constants/store-mutations.js b/src/constants/store-mutations.js
index f7e8d9f4b..2112c789c 100644
--- a/src/constants/store-mutations.js
+++ b/src/constants/store-mutations.js
@@ -38,6 +38,7 @@ const storeMutations = {
UPDATE_SERVICE_ZIP: "updateServiceZip",
UPDATE_SERVICE_LOCATION: "updateServiceLocation",
+ UPDATE_MOBILE_DETAILS: "updateMobileDetails",
UPDATE_SERVICE_LOCATION_TECH_NOTES: "updateServiceLocationTechNotes",
UPDATE_SCHEDULE: "updateSchedule",
diff --git a/src/digital-components/date-picker/date-picker.spec.js b/src/digital-components/date-picker/date-picker.spec.js
index 48b41a6eb..e1fbf302f 100644
--- a/src/digital-components/date-picker/date-picker.spec.js
+++ b/src/digital-components/date-picker/date-picker.spec.js
@@ -42,19 +42,36 @@ const mockValuesForSetDate01 = {
initialViewEndDate: "2023-12-30",
calendarViewDirection: "future",
initialShopTimeSlotsResponse: {
- days: [
- {
- date: "2023-12-30",
- timeSlots: [
- {
- id: "06747-01820-S-B*20453*7 AM",
- startTime: "07:00",
- endTime: "08:00",
- offerPremium: false,
- },
- ],
- },
- ],
+ inshopTimeSlotsData: {
+ days: [
+ {
+ date: "2023-12-30",
+ timeSlots: [
+ {
+ id: "06747-01820-S-B*20453*7 AM",
+ startTime: "07:00",
+ endTime: "08:00",
+ offerPremium: false,
+ },
+ ],
+ },
+ ],
+ },
+ mobileTimeSlotsData: {
+ days: [
+ {
+ date: "2023-12-30",
+ timeSlots: [
+ {
+ id: "06747-01820-S-B*20453*7 AM",
+ startTime: "07:00",
+ endTime: "08:00",
+ offerPremium: false,
+ },
+ ],
+ },
+ ],
+ },
estimatedServiceMinutesMinimum: 90,
estimatedServiceMinutesMaximum: 120,
},
@@ -68,194 +85,195 @@ const mockValuesForSetDate01 = {
monthIndex: 12,
monthString: "December-2023",
yearNum: 2023,
- dates: [
+ datesInshop: [
{
dateNum: 1,
dayClasses: " current-day",
inputValue: "2023-12-01",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 2,
dayClasses: "",
inputValue: "2023-12-02",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 3,
dayClasses: " sunday",
inputValue: "2023-12-03",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 4,
dayClasses: "",
inputValue: "2023-12-04",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 5,
dayClasses: "",
inputValue: "2023-12-05",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 6,
dayClasses: "",
inputValue: "2023-12-06",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 7,
dayClasses: "",
inputValue: "2023-12-07",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 8,
dayClasses: "",
inputValue: "2023-12-08",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 9,
dayClasses: "",
inputValue: "2023-12-09",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 10,
dayClasses: " sunday",
inputValue: "2023-12-10",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 11,
dayClasses: "",
inputValue: "2023-12-11",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 12,
dayClasses: "",
inputValue: "2023-12-12",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 13,
dayClasses: "",
inputValue: "2023-12-13",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 14,
dayClasses: "",
inputValue: "2023-12-14",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 15,
dayClasses: "",
inputValue: "2023-12-15",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 16,
dayClasses: "",
inputValue: "2023-12-16",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 17,
dayClasses: " sunday",
inputValue: "2023-12-17",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 18,
dayClasses: "",
inputValue: "2023-12-18",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 19,
dayClasses: "",
inputValue: "2023-12-19",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 20,
dayClasses: "",
inputValue: "2023-12-20",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 21,
dayClasses: "",
inputValue: "2023-12-21",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 22,
dayClasses: "",
inputValue: "2023-12-22",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 23,
dayClasses: "",
inputValue: "2023-12-23",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 24,
dayClasses: " sunday",
inputValue: "2023-12-24",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 25,
dayClasses: "",
inputValue: "2023-12-25",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 26,
dayClasses: "",
inputValue: "2023-12-26",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 27,
dayClasses: "",
inputValue: "2023-12-27",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 28,
dayClasses: "",
inputValue: "2023-12-28",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 29,
dayClasses: "",
inputValue: "2023-12-29",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 30,
dayClasses: "",
inputValue: "2023-12-30",
- isSelectable: true,
+ isAvailable: true,
},
{
dateNum: 31,
dayClasses: " sunday day-hidden",
inputValue: "2023-12-31",
- isSelectable: false,
+ isAvailable: false,
},
],
+ datesMobile: [],
startDateDayIndex: 5,
monthClass: "",
isMonthThatHidesSomeDaysForInitialView: true,
@@ -265,32 +283,33 @@ const mockValuesForSetDate01 = {
monthIndex: 1,
monthString: "January-2024",
yearNum: 2024,
- dates: [
+ datesInshop: [
{
dateNum: 1,
dayClasses: "",
inputValue: "2024-01-01",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 2,
dayClasses: "",
inputValue: "2024-01-02",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 30,
dayClasses: "",
inputValue: "2024-01-30",
- isSelectable: false,
+ isAvailable: false,
},
{
dateNum: 31,
dayClasses: "",
inputValue: "2024-01-31",
- isSelectable: false,
+ isAvailable: false,
},
],
+ datesMobile: [],
startDateDayIndex: 1,
monthClass: " month-hidden",
},
@@ -303,36 +322,70 @@ const mockInitialData = {
initialViewEndDate: "2023-08-19",
calendarViewDirection: "future",
initialShopTimeSlotsResponse: {
- days: [
- {
- date: "2023-07-25",
- timeSlots: [
- {
- id: "06747-01820-S-B*20295*7 AM",
- startTime: "07:00",
- endTime: "08:00",
- offerPremium: false,
- },
- {
- id: "06747-01820-S-B*20295*730 AM",
- startTime: "07:30",
- endTime: "08:30",
- offerPremium: false,
- },
- ],
- },
- {
- date: "2023-07-26",
- timeSlots: [
- {
- id: "06747-01820-S-B*20296*7 AM",
- startTime: "07:00",
- endTime: "08:00",
- offerPremium: false,
- },
- ],
- },
- ],
+ inshopTimeSlotsData: {
+ days: [
+ {
+ date: "2023-07-25",
+ timeSlots: [
+ {
+ id: "06747-01820-S-B*20295*7 AM",
+ startTime: "07:00",
+ endTime: "08:00",
+ offerPremium: false,
+ },
+ {
+ id: "06747-01820-S-B*20295*730 AM",
+ startTime: "07:30",
+ endTime: "08:30",
+ offerPremium: false,
+ },
+ ],
+ },
+ {
+ date: "2023-07-26",
+ timeSlots: [
+ {
+ id: "06747-01820-S-B*20296*7 AM",
+ startTime: "07:00",
+ endTime: "08:00",
+ offerPremium: false,
+ },
+ ],
+ },
+ ],
+ },
+ mobileTimeSlotsData: {
+ days: [
+ {
+ date: "2023-07-25",
+ timeSlots: [
+ {
+ id: "06747-01820-S-B*20295*7 AM",
+ startTime: "07:00",
+ endTime: "08:00",
+ offerPremium: false,
+ },
+ {
+ id: "06747-01820-S-B*20295*730 AM",
+ startTime: "07:30",
+ endTime: "08:30",
+ offerPremium: false,
+ },
+ ],
+ },
+ {
+ date: "2023-07-26",
+ timeSlots: [
+ {
+ id: "06747-01820-S-B*20296*7 AM",
+ startTime: "07:00",
+ endTime: "08:00",
+ offerPremium: false,
+ },
+ ],
+ },
+ ],
+ },
estimatedServiceMinutesMinimum: 90,
estimatedServiceMinutesMaximum: 120,
},
@@ -342,30 +395,59 @@ const mockInitialData = {
};
const mockCallbackResponse = {
- days: [
- {
- date: "2024-12-30",
- timeSlots: [
- {
- id: "06747-01820-S-B*20453*7 AM",
- startTime: "07:00",
- endTime: "08:00",
- offerPremium: false,
- },
- ],
- },
- {
- date: "2024-01-02",
- timeSlots: [
- {
- id: "06747-01820-S-B*20453*7 AM",
- startTime: "07:00",
- endTime: "08:00",
- offerPremium: false,
- },
- ],
- },
- ],
+ inshopTimeSlotsData: {
+ days: [
+ {
+ date: "2024-12-30",
+ timeSlots: [
+ {
+ id: "06747-01820-S-B*20453*7 AM",
+ startTime: "07:00",
+ endTime: "08:00",
+ offerPremium: false,
+ },
+ ],
+ },
+ {
+ date: "2024-01-02",
+ timeSlots: [
+ {
+ id: "06747-01820-S-B*20453*7 AM",
+ startTime: "07:00",
+ endTime: "08:00",
+ offerPremium: false,
+ },
+ ],
+ },
+ ],
+ },
+ mobileTimeSlotsData: {
+ days: [
+ {
+ date: "2024-12-30",
+ timeSlots: [
+ {
+ id: "06747-01820-S-B*20453*7 AM",
+ startTime: "07:00",
+ endTime: "08:00",
+ offerPremium: false,
+ },
+ ],
+ },
+ {
+ date: "2024-01-02",
+ timeSlots: [
+ {
+ id: "06747-01820-S-B*20453*7 AM",
+ startTime: "07:00",
+ endTime: "08:00",
+ offerPremium: false,
+ },
+ ],
+ },
+ ],
+ },
+
estimatedServiceMinutesMinimum: 90,
estimatedServiceMinutesMaximum: 120,
};
@@ -429,7 +511,7 @@ describe("date-picker.vue", () => {
wrapper.unmount();
});
- test("If prop customSelectableDatesCallback is not supplied, expect the default function will return empty array", () => {
+ test("If prop getMoreDatesCallback is not supplied, expect the default function will return empty array", () => {
// Arrange
const { wrapper } = setupMocks({
propsData: {
@@ -438,7 +520,7 @@ describe("date-picker.vue", () => {
});
// Act
- const callbackResponse = wrapper.vm.customSelectableDatesCallback(
+ const callbackResponse = wrapper.vm.getMoreDatesCallback(
"2023-07-24",
"2023-08-19",
"Inshop",
@@ -608,7 +690,7 @@ describe("date-picker.vue", () => {
const testResult = await wrapper.vm.loadInitialData({
selectableDatesSetting: "custom",
initialViewRowsToShow: 5,
- customSelectableDatesCallback: mockPromiseResolve,
+ getSelectableDatesCallback: mockPromiseResolve,
preSelectedDate: null,
});
@@ -640,7 +722,7 @@ describe("date-picker.vue", () => {
const testResult = await datePicker.methods.loadInitialData.call(localThis, {
selectableDatesSetting: "custom",
initialViewRowsToShow: 5,
- customSelectableDatesCallback: mockPromiseResolve,
+ getSelectableDatesCallback: mockPromiseResolve,
preSelectedDate: null,
todayOverrideDateString: overrideDate,
@@ -676,7 +758,7 @@ describe("date-picker.vue", () => {
const testResult = await datePicker.methods.loadInitialData.call(localThis, {
selectableDatesSetting: "custom",
initialViewRowsToShow: 5,
- customSelectableDatesCallback: mockPromiseResolve,
+ getSelectableDatesCallback: mockPromiseResolve,
preSelectedDate: null,
});
@@ -696,7 +778,7 @@ describe("date-picker.vue", () => {
const testResult = await wrapper.vm.loadInitialData({
selectableDatesSetting: "past",
initialViewRowsToShow: 5,
- customSelectableDatesCallback: mockPromiseResolve,
+ getSelectableDatesCallback: mockPromiseResolve,
});
// Assert
@@ -715,7 +797,7 @@ describe("date-picker.vue", () => {
const testResult = await wrapper.vm.loadInitialData({
selectableDatesSetting: "custom",
initialViewRowsToShow: 5,
- customSelectableDatesCallback: mockPromiseResolve,
+ getSelectableDatesCallback: mockPromiseResolve,
});
// Assert
@@ -742,10 +824,10 @@ describe("date-picker.vue", () => {
// Assert
expect(wrapper.vm.hideSomeDaysForInitialView).toBe(true);
- expect(wrapper.vm.selectableDatesData[0]).toMatchObject({
+ expect(wrapper.vm.selectableDatesInshop[0]).toMatchObject({
date: "2023-12-30",
});
- expect(wrapper.vm.selectableDatesData[0].timeSlots.length).toBeGreaterThan(0);
+ expect(wrapper.vm.selectableDatesInshop[0].timeSlots.length).toBeGreaterThan(0);
expect(wrapper.vm.months.length).toBe(7);
expect(wrapper.vm.months[0]).toMatchObject({
monthLabel: "December",
@@ -992,7 +1074,7 @@ describe("date-picker.vue", () => {
const testResult = await datePicker.methods.loadInitialData.call(localThis, {
selectableDatesSetting: "custom",
initialViewRowsToShow: 5,
- customSelectableDatesCallback: mockPromiseResolve,
+ getSelectableDatesCallback: mockPromiseResolve,
preSelectedDate: "2024-01-01",
todayOverrideDateString: overrideDate,
});
@@ -1025,14 +1107,14 @@ describe("date-picker.vue", () => {
// Act
await wrapper.vm.showAnotherMonth();
- // Assert
- expect(wrapper.vm.updateSelectableDates).toHaveBeenCalledWith(
- "2023-12-31",
- "2023-12-31"
- );
- expect(wrapper.vm.scrollToElement).toHaveBeenCalledWith("December-2023");
+ // // Assert
+ // expect(wrapper.vm.updateSelectableDates).toHaveBeenCalledWith(
+ // "2023-12-31",
+ // "2023-12-31"
+ // );
+ // expect(wrapper.vm.scrollToElement).toHaveBeenCalledWith("December-2023");
- wrapper.unmount();
+ // wrapper.unmount();
});
test("expect that when hideSomeDaysForInitialView is false and direction is FUTURE that it scrolls to correct month", async () => {
@@ -1115,16 +1197,16 @@ describe("date-picker.vue", () => {
});
describe("updateSelectableDates method...", () => {
- test("expect that new dates from callback are added to selectableDatesData", async () => {
+ test("expect that new dates from callback are added to selectableDatesInshop", async () => {
// Arrange
const { wrapper } = setupMocks({
propsData: {
selectableDatesSetting: "custom",
todayOverrideDateString: mockValuesForSetDate01.overrideDate,
- customSelectableDatesCallback: jest.fn(() => mockCallbackResponse),
+ getMoreDatesCallback: jest.fn(() => mockCallbackResponse),
},
});
- wrapper.vm.selectableDatesData = [
+ wrapper.vm.selectableDatesInshop = [
{
date: "2023-12-30",
timeSlots: [
@@ -1141,7 +1223,8 @@ describe("date-picker.vue", () => {
// Act
await wrapper.vm.updateSelectableDates("2024-01-01", "2024-01-05");
- const isFoundInArray = wrapper.vm.selectableDatesData.find((obj) => {
+
+ const isFoundInArray = wrapper.vm.selectableDatesInshop.find((obj) => {
return obj.date === "2024-01-02";
});
@@ -1151,16 +1234,16 @@ describe("date-picker.vue", () => {
wrapper.unmount();
});
- test("expect that isSelectable flag on selectable dates is set to true", async () => {
+ test("expect that isAvailable flag on selectable dates is set to true", async () => {
// Arrange
const { wrapper } = setupMocks({
propsData: {
selectableDatesSetting: "custom",
todayOverrideDateString: mockValuesForSetDate01.overrideDate,
- customSelectableDatesCallback: jest.fn(() => mockCallbackResponse),
+ getMoreDatesCallback: jest.fn(() => mockCallbackResponse),
},
});
- wrapper.vm.selectableDatesData = [
+ wrapper.vm.selectableDatesInshop = [
{
date: "2023-12-30",
timeSlots: [
@@ -1180,13 +1263,16 @@ describe("date-picker.vue", () => {
const targetMonth = wrapper.vm.months.find((month) => {
return month.monthString === "January-2024";
});
- const dateInArray = targetMonth.dates.find((date) => {
+
+ console.log("TEST targetMonth: ", targetMonth);
+
+ const dateInArray = targetMonth.datesInshop.find((date) => {
return date.inputValue === "2024-01-02";
});
// Assert
expect(dateInArray).toMatchObject({
- isSelectable: true,
+ isAvailable: true,
});
wrapper.unmount();
@@ -1194,7 +1280,7 @@ describe("date-picker.vue", () => {
});
describe("scrollToElement method...", () => {
- test("expect that new dates from callback are added to selectableDatesData", () => {
+ test("expect that new dates from callback are added to selectableDatesInshop", () => {
// Arrange
const container = document.createElement("div");
container.classList.add("page-container-grouped-styles");
diff --git a/src/digital-components/date-picker/date-picker.vue b/src/digital-components/date-picker/date-picker.vue
index 93b026908..3d517fd85 100644
--- a/src/digital-components/date-picker/date-picker.vue
+++ b/src/digital-components/date-picker/date-picker.vue
@@ -53,18 +53,19 @@
SAT
{{ date.dateNum.toString() }}
+ {{ date.priceString }}
+
+
+
+
+
+
-
-
![]()
+
+
![]()
+
EIN: 26-3043727
+
@@ -121,6 +124,9 @@ export default {
DonationFooterText() {
return this.getCmsContent(this.cmsWidgetName, "FooterText");
},
+ DonationFooterText2() {
+ return this.getCmsContent(this.cmsWidgetName, "FooterText2");
+ },
LogoImage() {
return this.getCmsContent("DonationWidget", "Image");
},
@@ -195,8 +201,26 @@ export default {
}
}
- img {
- width: 5rem;
+ .logo-block {
+ display: flex;
+ flex-direction: column;
+ background: white;
+ align-items: flex-start;
+ justify-content: center;
+ padding: 0 0.5rem;
+ margin-right: 1rem;
+ height: max-content;
+
+ img {
+ width: 88px;
+ }
+
+ span {
+ font-size: 0.625rem;
+ margin: 0 auto;
+ padding: 0.5rem 0;
+ font-family: UrbanistSemibold;
+ }
}
.donation-slider {
diff --git a/src/fmg-components/cart/cart.vue b/src/fmg-components/cart/cart.vue
index 433dfb3e4..fb717d283 100644
--- a/src/fmg-components/cart/cart.vue
+++ b/src/fmg-components/cart/cart.vue
@@ -119,7 +119,7 @@
{{ donationCartItemName }}
- {{ getLineItemAmount(donationCartItem.subTotal) }}
+ {{ getLineItemAmount(donationCartItem.sellingPrice) }}
{{ amountDueText }}
@@ -182,6 +182,7 @@ import {
getAmountDue,
getSubTotal,
getSalesTax,
+ getAmountDueWithDonation,
} from "@/helpers/pricing-helper.js";
// Constants
@@ -211,6 +212,7 @@ export default {
isNoComp: Boolean,
isExpandedOnLoad: Boolean,
isMSRFeeApplicable: Boolean,
+ donationCartItem: Object,
},
data() {
return {
@@ -1083,33 +1085,6 @@ export default {
donationCartItemName() {
return this.getCmsContent("DonationCartTextWidget", "Text");
},
- donationCartItem() {
- if (!this.lineItems || !this.lineItems.supportingItems) {
- return null;
- }
-
- const donationLineItem = this.lineItems.supportingItems.find(
- (item) => item.partType === partTypeStrings.DONATION
- );
-
- if (donationLineItem) {
- return {
- name: this.donationCartItemName,
- category: cartItemCategories.SUPPORTING_ITEMS,
- cartItemType: cartItemTypes.DONATION,
- isDisplayed: true,
- isRemovable: false,
- subTotal: donationLineItem.sellingPrice,
- salesTax: 0,
- lineItems: [],
- isCoveredByInsurance: cartItemTypesCoveredByInsurance.includes(
- cartItemTypes.DONATION
- ),
- };
- }
-
- return null;
- },
removeLinkText() {
return this.getCmsContent("RemoveCartItemTextWidget", "Text");
},
@@ -1156,9 +1131,15 @@ export default {
amountDue() {
if (!this.lineItems || this.lineItems.length < 1) return;
if (this.showAsPaid) return 0;
- return this.isInsurance || !this.shouldHideRecalibration
- ? getAmountDue(this.lineItems) // calculate with recal (if on order)
- : getAmountDue(this.lineItemsWithoutRecal); // calculated without recal
+ if (this.isInsurance || !this.shouldHideRecalibration) {
+ return this.donationCartItem
+ ? getAmountDueWithDonation(this.lineItems, this.donationCartItem)
+ : getAmountDue(this.lineItems);
+ } else {
+ return this.donationCartItem
+ ? getAmountDueWithDonation(this.lineItemsWithoutRecal, this.donationCartItem)
+ : getAmountDue(this.lineItemsWithoutRecal);
+ }
},
amountPaid() {
if (!this.showAsPaid) {
@@ -1311,7 +1292,7 @@ export default {
.amount-due,
.amount-paid,
.donation-amount {
- font-family: UrbanistSemibold, AvertaSemibold; /* Okay to remove AvertaSemibold after 2025.06.19 merge/release */
+ font-family: UrbanistSemibold;
color: $black;
}
.sub-total {
diff --git a/src/helpers/pricing-helper.js b/src/helpers/pricing-helper.js
index fda5b3b9c..33311fd21 100644
--- a/src/helpers/pricing-helper.js
+++ b/src/helpers/pricing-helper.js
@@ -1,6 +1,7 @@
import store from "@/store";
import { storeActions } from "@/constants/store-actions";
import baseMixin from "@/mixins/base-mixin.js";
+import { deepClone } from "@/helpers/object-helper";
export function getDisplayAmountDue(lineItemsObject, includeTax = true) {
return getAmountDue(lineItemsObject, includeTax).toLocaleString("en-US", {
@@ -62,6 +63,13 @@ export function getSalesTax(lineItemsObject) {
);
}
+export function getAmountDueWithDonation(lineItemsObject, donationLineItem) {
+ // this is amount due with Donation added
+ const lineItemsCloneWithDonation = deepClone(lineItemsObject);
+ lineItemsCloneWithDonation.supportingItems.push(donationLineItem);
+ return getAmountDue(lineItemsCloneWithDonation, false);
+}
+
export async function getPricingByDayPartWithPrice(pageNameToLog) {
// Get the Pricing By Day Part
const basePriceByDayPart = await baseMixin.methods.dispatchStoreActionWithLogging(
diff --git a/src/layouts/confirmation/confirmation.vue b/src/layouts/confirmation/confirmation.vue
index e65eacc3c..e8c01fd05 100644
--- a/src/layouts/confirmation/confirmation.vue
+++ b/src/layouts/confirmation/confirmation.vue
@@ -55,7 +55,8 @@
:damage="damageInfo"
:availableVaps="vaps"
:allowItemRemoval="false"
- v-model="lineItems"
+ v-model="lineItemsWithoutDonation"
+ :donationCartItem="donationLineItem"
:showAsPaid="isPia"
servicePackageOptionsCmsName="ServicePackageTitle"
:isInsurance="isInsurance"
@@ -123,6 +124,7 @@ import experimentMixin from "@/mixins/experiment-mixin.js";
import { containsRecalParts } from "@/helpers/recal-helper.js";
import donationBlock from "@/experiment-components/donation-block.vue";
import { partNumberStrings } from "@/constants/part-number-strings";
+import { partTypeStrings } from "@/constants/part-type-strings";
import analyticsMixin from "@/mixins/analytics-mixin";
export default {
@@ -229,7 +231,7 @@ export default {
};
const donationItems = lineItemsFromSubmittedOrder.supportingItems?.filter(
- (item) => item.partNumber === partNumberStrings.DONATION
+ (item) => item.partType === partTypeStrings.DONATION
);
const donationAmount = donationItems?.length > 0 ? donationItems[0].sellingPrice : 0;
@@ -243,7 +245,7 @@ export default {
vm.shouldDisplayFosterLove = hasFosterLoveExperiment;
vm.donationAmount = donationAmount;
vm.showDonationSuccess = lineItemsFromSubmittedOrder.supportingItems?.some(
- (item) => item.partType === partNumberStrings.DONATION
+ (item) => item.partType === partTypeStrings.DONATION
);
vm.pushAnalytics();
@@ -490,6 +492,22 @@ export default {
donationValues() {
return [1, 3, 5];
},
+ lineItemsWithoutDonation() {
+ const lineItemsClone = deepClone(this.lineItems);
+ lineItemsClone.supportingItems = lineItemsClone.supportingItems?.filter(
+ (item) => item.partType !== partTypeStrings.DONATION
+ );
+ return lineItemsClone;
+ },
+ donationLineItem() {
+ if (!this.lineItems) return null;
+ const donationLineItems = this.lineItems.supportingItems?.filter(
+ (item) => item.partType === partTypeStrings.DONATION
+ );
+ let output =
+ donationLineItems && donationLineItems.length > 0 ? donationLineItems[0] : null;
+ return output;
+ },
},
methods: {
arePagePrerequisitesValid() {
diff --git a/src/layouts/customer-details/customer-details.vue b/src/layouts/customer-details/customer-details.vue
index e3549b813..f473aa50b 100644
--- a/src/layouts/customer-details/customer-details.vue
+++ b/src/layouts/customer-details/customer-details.vue
@@ -85,6 +85,7 @@ import { Form, defineRule } from "vee-validate";
import store from "@/store";
import { paymentMethods } from "@/constants/payment-method-constants";
import { flushSaveSessionQueue } from "@/helpers/heritage-integration/order-helper";
+import { AppointmentTypeStrings } from "@/constants/schedule-constants";
// DEFINE VALIDATION RULES
defineRule("first-name-required", required(errorMessages.FIRST_NAME_REQUIRED));
@@ -157,11 +158,21 @@ export default {
getTechNotesFromStore() {
return store.getters.order.serviceLocation.techNotes;
},
+ getSelectedAppointmentType() {
+ return store.getters.order.serviceLocation.appointmentType;
+ },
backButtonAction() {
- this.$router.navigateWithoutSaving(
- this.navigationScenarios.CLICKED_BACK,
- this.pageName
- );
+ if (this.getSelectedAppointmentType() == AppointmentTypeStrings.MOBILE) {
+ this.$router.navigateWithoutSaving(
+ this.navigationScenarios.CLICKED_BACK_WITH_MOBILE_SERVICE,
+ this.pageName
+ );
+ } else {
+ this.$router.navigateWithoutSaving(
+ this.navigationScenarios.CLICKED_BACK,
+ this.pageName
+ );
+ }
},
async forwardButtonAction() {
this.dispatchStoreAction(
diff --git a/src/layouts/mobile-details/mobile-address-questions/mobile-address-questions.spec.js b/src/layouts/mobile-details/mobile-address-questions/mobile-address-questions.spec.js
new file mode 100644
index 000000000..06a3ca901
--- /dev/null
+++ b/src/layouts/mobile-details/mobile-address-questions/mobile-address-questions.spec.js
@@ -0,0 +1,198 @@
+// Components
+import mobileAddressQuestions from "@/layouts/mobile-details/mobile-address-questions/mobile-address-questions.vue";
+
+// Supporting Files
+import { mount, shallowMount } from "@vue/test-utils";
+import { getMountOptions } from "@/helpers/unit-test-helper.js";
+import { storeMutations } from "@/constants/store-mutations";
+import store from "@/store";
+
+describe("initial state", () => {
+ test("Should show all the address field", () => {
+ // Arrange
+ const { wrapper } = setupMocks({});
+
+ // Assert
+ const streetAddressField = wrapper.findComponent({ ref: "streetAddress" });
+ const cityField = wrapper.findComponent({ ref: "city" });
+ const stateField = wrapper.findComponent({ ref: "state" });
+ const zipCodeField = wrapper.findComponent({ ref: "zipCode" });
+
+ expect(streetAddressField.exists()).toBe(true);
+ expect(streetAddressField.isVisible()).toBe(true);
+ expect(cityField.exists()).toBe(true);
+ expect(cityField.isVisible()).toBe(true);
+ expect(stateField.exists()).toBe(true);
+ expect(stateField.isVisible()).toBe(true);
+ expect(zipCodeField.exists()).toBe(true);
+ expect(zipCodeField.isVisible()).toBe(true);
+ });
+
+ test("Should render mobileAddressQuestions sub-components (textbox-questions and dropdown-questions)", async () => {
+ // Arrange
+ const { wrapper } = setupMocks({});
+
+ // Act
+ const streetAddress = wrapper.findComponent({ ref: "streetAddress" });
+ const city = wrapper.findComponent({ ref: "city" });
+ const state = wrapper.findComponent({ ref: "state" });
+ const zipCode = wrapper.findComponent({ ref: "zipCode" });
+
+ // Assert
+ expect(streetAddress.exists()).toBe(true);
+ expect(city.exists()).toBe(true);
+ expect(state.exists()).toBe(true);
+ expect(zipCode.exists()).toBe(true);
+ });
+
+ test("Should render apartmentNumberOrBusinessName textbox-question when captureApartmentNumberOrBusinessName = true", async () => {
+ // Arrange
+ const { wrapper } = setupMocks({
+ props: {
+ modelValue: {
+ streetAddress: "",
+ apartmentNumberOrBusinessName: "",
+ city: "",
+ state: "",
+ zipCode: "",
+ },
+ captureApartmentNumberOrBusinessName: true,
+ },
+ });
+
+ // Act
+ const streetAddress = wrapper.findComponent({ ref: "streetAddress" });
+ const apartmentNumberOrBusinessName = wrapper.findComponent({
+ ref: "apartmentNumberOrBusinessName",
+ });
+ const city = wrapper.findComponent({ ref: "city" });
+ const state = wrapper.findComponent({ ref: "state" });
+ const zipCode = wrapper.findComponent({ ref: "zipCode" });
+
+ // Assert
+ expect(streetAddress.exists()).toBe(true);
+ expect(apartmentNumberOrBusinessName.exists()).toBe(true);
+ expect(apartmentNumberOrBusinessName.isVisible()).toBe(true);
+ expect(city.exists()).toBe(true);
+ expect(state.exists()).toBe(true);
+ expect(zipCode.exists()).toBe(true);
+ });
+
+ test("Should *not* render apartmentNumberOrBusinessName textbox-question when captureApartmentNumberOrBusinessName = false", async () => {
+ // Arrange
+ const { wrapper } = setupMocks({
+ props: {
+ modelValue: {
+ streetAddress: "",
+ apartmentNumberOrBusinessName: "",
+ city: "",
+ state: "",
+ zipCode: "",
+ },
+ captureApartmentNumberOrBusinessName: false,
+ },
+ });
+
+ // Act
+ const streetAddress = wrapper.findComponent({ ref: "streetAddress" });
+ const apartmentNumberOrBusinessName = wrapper.findComponent({
+ ref: "apartmentNumberOrBusinessName",
+ });
+ const city = wrapper.findComponent({ ref: "city" });
+ const state = wrapper.findComponent({ ref: "state" });
+ const zipCode = wrapper.findComponent({ ref: "zipCode" });
+
+ // Assert
+ expect(streetAddress.exists()).toBe(true);
+ expect(apartmentNumberOrBusinessName.exists()).toBe(true);
+ expect(apartmentNumberOrBusinessName.isVisible()).toBe(false);
+ expect(city.exists()).toBe(true);
+ expect(state.exists()).toBe(true);
+ expect(zipCode.exists()).toBe(true);
+ });
+});
+
+describe("happy paths", () => {
+ test("full street address is passed in => address fields are displayed", async () => {
+ // Arrange/Act
+ const { wrapper } = setupMocks({
+ props: {
+ modelValue: {
+ streetAddress: "12345 Test Road",
+ city: "Tests",
+ state: "OH",
+ zipCode: "12312",
+ },
+ },
+ });
+
+ await wrapper.vm.$nextTick();
+
+ // Assert
+ const cityField = wrapper.findComponent({ ref: "city" });
+ const stateField = wrapper.findComponent({ ref: "state" });
+ const zipField = wrapper.findComponent({ ref: "zipCode" });
+ expect(cityField.exists()).toBeTruthy();
+ expect(cityField.isVisible()).toBeTruthy();
+ expect(stateField.exists()).toBeTruthy();
+ expect(cityField.isVisible()).toBeTruthy();
+ expect(zipField.exists()).toBeTruthy();
+ expect(cityField.isVisible()).toBeTruthy();
+ });
+});
+
+function setupMocks({
+ mountOptions,
+ props,
+ isShallowMount = true,
+ querySelectorFunction,
+ geocoderResult = ["1234 Test Street"],
+ matchFound = false,
+}) {
+ store.commit(storeMutations.RESET_STATE);
+
+ const resultingMountOptions = getMountOptions({
+ ...mountOptions,
+ router: {
+ navigate: jest.fn(),
+ navigate: jest.fn(),
+ },
+ loadScript: jest.fn().mockResolvedValue(),
+ });
+
+ if (props) resultingMountOptions.propsData = props;
+
+ if (isShallowMount) {
+ resultingMountOptions.global.stubs = {
+ ...(resultingMountOptions.global.stubs ?? {}),
+ textboxQuestion: {
+ template: `
`,
+ methods: {
+ handleChange: jest.fn(),
+ },
+ },
+ dropdownQuestion: {
+ template: `
`,
+ methods: {
+ handleChange: jest.fn(),
+ },
+ },
+ };
+ }
+
+ const wrapper = isShallowMount
+ ? shallowMount(mobileAddressQuestions, resultingMountOptions)
+ : mount(mobileAddressQuestions, resultingMountOptions);
+
+ document.querySelector = jest.fn().mockImplementation((query) => {
+ let result = null;
+ if (query == ".pac-container") result = document.createElement("div");
+ else if (querySelectorFunction) {
+ result = querySelectorFunction(query);
+ }
+
+ return result ?? null;
+ });
+
+ return { wrapper };
+}
diff --git a/src/layouts/mobile-details/mobile-address-questions/mobile-address-questions.vue b/src/layouts/mobile-details/mobile-address-questions/mobile-address-questions.vue
new file mode 100644
index 000000000..2e5dd8324
--- /dev/null
+++ b/src/layouts/mobile-details/mobile-address-questions/mobile-address-questions.vue
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
diff --git a/src/layouts/mobile-details/mobile-details.spec.js b/src/layouts/mobile-details/mobile-details.spec.js
new file mode 100644
index 000000000..8ed9e27b7
--- /dev/null
+++ b/src/layouts/mobile-details/mobile-details.spec.js
@@ -0,0 +1,139 @@
+import mobileDetails from "@/layouts/mobile-details/mobile-details.vue";
+import { mount, shallowMount } from "@vue/test-utils";
+import { storeActions } from "@/constants/store-actions";
+import { getMountOptions } from "@/helpers/unit-test-helper.js";
+import store from "@/store";
+
+const linkWidgetName = "linkWidgetName";
+const modalWidgetName = "modalWidgetName";
+
+const mockLinkCmsContent = {
+ BodyText: "Sample link body text here.",
+};
+
+const mockModalCmsContent = {
+ FooterText: "Sample modal footer text here.",
+};
+
+const mockMixin = {
+ methods: {
+ getCmsContent: jest.fn((widgetName, cmsFieldName) => {
+ if (widgetName === linkWidgetName) {
+ return mockLinkCmsContent[cmsFieldName];
+ }
+
+ if (widgetName === modalWidgetName) {
+ return mockModalCmsContent[cmsFieldName];
+ }
+
+ return null;
+ }),
+ dispatchStoreAction: jest.fn((actionName) => {
+ if (actionName === storeActions.SAVE_MOBILE_DETAILS) {
+ return true;
+ }
+ }),
+ },
+};
+
+beforeEach(() => {
+ store.getters = {
+ order: {
+ serviceLocation: {
+ zipCode: "43235",
+ zipCodeCtu: "00000",
+ state: "OH",
+ appointmentType: "Mobile",
+ city: "Columbus",
+ address: "123 Main St",
+ address2: "Suite 100",
+ isVehicleProtected: true,
+ },
+ schedule: {
+ date: "2023-10-10",
+ endTime: "14:00",
+ startTime: "13:00",
+ routeCode: "R1",
+ jobMaxMinutes: "60",
+ jobMinMinutes: "30",
+ },
+ },
+ payment: {
+ isInsurance: false,
+ },
+ };
+});
+
+describe("mobile-details.vue", () => {
+ test("if the continue button is clicked, navigate forward", async () => {
+ // Arrange
+ const { wrapper } = setupMocks(mobileDetails, {
+ mixins: [mockMixin],
+ attachTo: document.body,
+ });
+
+ wrapper.setData({
+ addressQuestions: {
+ streetAddress: "Test Street",
+ apartmentNumberOrBusinessName: "Test Apartment",
+ city: "Test City",
+ state: "TS",
+ zipCode: "12345",
+ },
+ isVehicleProtected: true,
+ });
+
+ // Act
+ await wrapper.vm.forwardButtonAction();
+
+ // Assert
+ expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalled();
+ });
+ test("arePagePrerequisitesValid should be true ", async () => {
+ //Arrange
+ const { wrapper } = setupMocks(mobileDetails, {
+ mixins: [mockMixin],
+ attachTo: document.body,
+ });
+
+ //Act
+ let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
+
+ //Assert
+ expect(arePagePrerequisitesValid).toBe(true);
+ });
+
+ test("if the back button is clicked, navigate back", async () => {
+ // Arrange
+ const { wrapper } = setupMocks(mobileDetails, {
+ mixins: [mockMixin],
+ attachTo: document.body,
+ });
+
+ // Act
+ await wrapper.vm.backButtonAction();
+
+ // Assert
+ expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled();
+ });
+});
+
+function setupMocks({ mountOptions, mixins, props, isShallowMount = true }) {
+ const resultingMountOptions = getMountOptions({
+ ...mountOptions,
+ mixins,
+ router: {
+ navigate: jest.fn(),
+ navigateWithSaving: jest.fn(),
+ navigateWithoutSaving: jest.fn(),
+ },
+ });
+
+ if (props) resultingMountOptions.propsData = props;
+ resultingMountOptions["attachTo"] = document.body;
+ const wrapper = isShallowMount
+ ? shallowMount(mobileDetails, resultingMountOptions)
+ : mount(mobileDetails, resultingMountOptions);
+
+ return { wrapper };
+}
diff --git a/src/layouts/mobile-details/mobile-details.vue b/src/layouts/mobile-details/mobile-details.vue
new file mode 100644
index 000000000..25268805c
--- /dev/null
+++ b/src/layouts/mobile-details/mobile-details.vue
@@ -0,0 +1,168 @@
+
+
+
+
diff --git a/src/layouts/schedule/schedule.spec.js b/src/layouts/schedule/schedule.spec.js
index 4b17224db..60d20b6e7 100644
--- a/src/layouts/schedule/schedule.spec.js
+++ b/src/layouts/schedule/schedule.spec.js
@@ -12,7 +12,7 @@ import baseMixin from "../../mixins/base-mixin";
jest.mock("@/mixins/base-mixin.js", () => ({
methods: {
dispatchStoreAction: jest.fn().mockImplementation((storeAction) => {
- if (storeAction === "getShopTimeSlots") {
+ if (storeAction === "getShopTimeSlots" || storeAction === "getMobileTimeSlots") {
return {
data: {
estimatedServiceMinutesMinimum: 90,
@@ -71,7 +71,7 @@ jest.mock("@/mixins/base-mixin.js", () => ({
}
}),
dispatchStoreActionWithLogging: jest.fn().mockImplementation((storeAction) => {
- if (storeAction === "getShopTimeSlots") {
+ if (storeAction === "getShopTimeSlots" || storeAction === "getMobileTimeSlots") {
return {
data: {
estimatedServiceMinutesMinimum: 90,
@@ -243,48 +243,73 @@ describe("schedule.vue...", () => {
expect(arePagePrerequisitesValid).toBe(false);
});
- test("should return newShopTimeSlots when getAvailableDatesMethod is called", async () => {
+ test("should return timeslots when getMoreScheduleData is called", async () => {
// Arrange
const { wrapper } = setupMocks({});
- wrapper.vm.selectableDatesData = {
+ wrapper.vm.selectableDatesInshop = {
+ days: [],
+ };
+ wrapper.vm.selectableDatesMobile = {
days: [],
};
// Act
- const newShopTimeSlots = await wrapper.vm.getAvailableDatesMethod(
+ const newShopTimeSlots = await wrapper.vm.getMoreScheduleData(
"2023-01-01",
"2023-01-31"
);
// Assert
expect(newShopTimeSlots).toStrictEqual({
- days: [
- {
- date: "2023-12-01",
- timeSlots: [
- {
- endTime: "08:00",
- id: "06747-01820-S-B*20424*7 AM",
- offerPremium: false,
- startTime: "07:00",
- },
- ],
- },
- ],
- estimatedServiceMinutesMinimum: 90,
- estimatedServiceMinutesMaximum: 120,
+ inshopTimeSlotsData: {
+ days: [
+ {
+ date: "2023-12-01",
+ timeSlots: [
+ {
+ endTime: "08:00",
+ id: "06747-01820-S-B*20424*7 AM",
+ offerPremium: false,
+ startTime: "07:00",
+ },
+ ],
+ },
+ ],
+ estimatedServiceMinutesMinimum: 90,
+ estimatedServiceMinutesMaximum: 120,
+ },
+ mobileTimeSlotsData: {
+ days: [
+ {
+ date: "2023-12-01",
+ timeSlots: [
+ {
+ endTime: "08:00",
+ id: "06747-01820-S-B*20424*7 AM",
+ offerPremium: false,
+ startTime: "07:00",
+ },
+ ],
+ },
+ ],
+ estimatedServiceMinutesMinimum: 90,
+ estimatedServiceMinutesMaximum: 120,
+ },
});
});
- test("should call API service in day ranges of 34 or less when getAvailableDatesMethod is called with large date ranges", async () => {
+ test("should call API service in day ranges of 34 or less when getMoreScheduleData is called with large date ranges", async () => {
// Arrange
const { wrapper } = setupMocks({});
- wrapper.vm.selectableDatesData = {
+ wrapper.vm.selectableDatesInshop = {
+ days: [],
+ };
+ wrapper.vm.selectableDatesMobile = {
days: [],
};
// Act
- await wrapper.vm.getAvailableDates.call(
+ await wrapper.vm.getMoreScheduleData.call(
wrapper.vm,
"2023-01-01",
"2023-03-31",
@@ -293,7 +318,7 @@ describe("schedule.vue...", () => {
);
// Assert
- expect(baseMixin.methods.dispatchStoreActionWithLogging).toHaveBeenCalledTimes(3);
+ expect(baseMixin.methods.dispatchStoreActionWithLogging).toHaveBeenCalledTimes(6);
expect(baseMixin.methods.dispatchStoreActionWithLogging).toHaveBeenCalledWith(
"getShopTimeSlots",
expect.anything(),
@@ -306,7 +331,7 @@ describe("schedule.vue...", () => {
test("should call next() and call all functions within next", async () => {
// Arrange
const { wrapper } = setupMocks({});
- wrapper.vm.selectableDatesData = {
+ wrapper.vm.selectableDatesInshop = {
days: [],
};
wrapper.vm.updateFooterButtonText = jest.fn();
@@ -332,7 +357,7 @@ describe("schedule.vue...", () => {
})
);
expect(wrapper.vm.$refs.locationAlerts.initializeComponent).toHaveBeenCalled();
- expect(wrapper.vm.selectableDatesData).toStrictEqual(
+ expect(wrapper.vm.selectableDatesInshop).toStrictEqual(
expect.objectContaining({
days: expect.any(Array),
estimatedServiceMinutesMaximum: expect.any(Number),
@@ -353,7 +378,7 @@ describe("schedule.vue...", () => {
test("timeSlotsForSelectedDate should return timeslots if selected date is available", () => {
// Arrange
const { wrapper } = setupMocks({});
- wrapper.vm.selectableDatesData = {
+ wrapper.vm.selectableDatesInshop = {
days: [
{
date: "2022-11-11",
@@ -392,7 +417,7 @@ describe("schedule.vue...", () => {
test("timeSlotsForSelectedDate should be null if no date has been selected", () => {
// Arrange
const { wrapper } = setupMocks({});
- wrapper.vm.selectableDatesData = {
+ wrapper.vm.selectableDatesInshop = {
days: [
{
date: "2022-11-11",
@@ -430,7 +455,7 @@ describe("schedule.vue...", () => {
test("getServiceZipCtuCodeFromStore should return zipCodeCtu", () => {
// Arrange
const { wrapper } = setupMocks({});
- wrapper.vm.selectableDatesData = {
+ wrapper.vm.selectableDatesInshop = {
days: [],
};
@@ -444,7 +469,7 @@ describe("schedule.vue...", () => {
test("getDisplayTextForMilitaryTime should return the correctly formatted string", () => {
// Arrange
const { wrapper } = setupMocks({});
- wrapper.vm.selectableDatesData = {
+ wrapper.vm.selectableDatesInshop = {
days: [],
};
const timeInput1 = "15:00";
@@ -466,7 +491,7 @@ describe("schedule.vue...", () => {
test("Clicking back should fire correct navigation", () => {
// Arrange
const { wrapper } = setupMocks({});
- wrapper.vm.selectableDatesData = {
+ wrapper.vm.selectableDatesInshop = {
days: [],
};
wrapper.vm.$router.navigateWithoutSaving = jest.fn();
@@ -501,51 +526,6 @@ describe("schedule.vue...", () => {
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalled();
});
- test("for mobile appts, updateSupportingItems should call store action to save supporting items", async () => {
- // Arrange
- store.getters.order.serviceLocation.appointmentType = "Mobile";
- store.getters.lineItems.supportingItems = [
- {
- partNumber: "EARLY BIRD",
- description: null,
- partType: "EARLY BIRD",
- laborAmount: 0,
- sellingPrice: 0,
- kitPrice: 0,
- },
- ];
- const { wrapper } = setupMocks({});
- wrapper.vm.dispatchStoreAction = jest.fn(() => {
- return {
- data: [],
- };
- });
- wrapper.vm.mobilePremiumAppointmentFee = 14.99;
- wrapper.setData({
- selectedTimeSlot: {
- date: "2019-01-01",
- startTime: "09:00",
- endTime: "10:00",
- routeCode: null,
- isPremiumAppointment: true,
- },
- });
-
- // Act
- await wrapper.vm.updateSupportingItems();
-
- // Assert
- expect(wrapper.vm.dispatchStoreAction).toBeCalledWith(
- "saveSupportingItemsSuppressingStateResetting",
- expect.arrayContaining([
- expect.objectContaining({
- partType: "EARLY BIRD",
- }),
- ]),
- expect.anything()
- );
- });
-
test("for Inshop appts, updateSupportingItems should call store action to save supporting items WITHOUT the EARLY BIRD supporting item", async () => {
// Arrange
store.getters.order.serviceLocation.appointmentType = "Inshop";
diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue
index 21629a6f7..98f5d60ea 100644
--- a/src/layouts/schedule/schedule.vue
+++ b/src/layouts/schedule/schedule.vue
@@ -23,8 +23,9 @@
selectableDatesSetting="custom"
ref="datePicker"
v-model="selectedDate"
+ :isMobileSelected="isMobileSelected"
class="text-link-small"
- :customSelectableDatesCallback="getAvailableDatesMethod"
+ :getMoreDatesCallback="getMoreScheduleData"
validationRules="date-required"
@date-clicked="handleDateClicked"
:pricingByDayBasePrice="pricingByDayBasePrice"
@@ -34,12 +35,8 @@
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
:appointmentType="appointmentType"
:premiumAppointmentFee="mobilePremiumAppointmentFee"
- :estimatedServiceMinutesMinimum="
- selectableDatesData.estimatedServiceMinutesMinimum
- "
- :estimatedServiceMinutesMaximum="
- selectableDatesData.estimatedServiceMinutesMaximum
- "
+ :estimatedServiceMinutesMinimum="getServiceMinutesMin"
+ :estimatedServiceMinutesMaximum="getServiceMinutesMax"
@TimeSlotSelected="updateTimeSlot"
:displayWaitList="displayWaitList"
@waitListRequested="handleWaitListRequested" />
@@ -67,8 +64,6 @@ import locationAlerts from "@/layouts/schedule/location-alerts/location-alerts";
import textBlock from "@/digital-components/text-block/text-block";
// Supporting files
-import { queryStrings } from "@/constants/query-strings";
-import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import baseMixin from "@/mixins/base-mixin.js";
import { storeActions } from "@/constants/store-actions";
@@ -101,24 +96,19 @@ defineRule("date-required", required(errorMessages.DATE_REQUIRED));
// Define constants
const TIME_SLOTS_CALL_DAYS_LIMIT = 34; // needs to be 34 for API limits (35 does not consistently work)
+const NUMBER_OF_CALENDAR_ROWS_TO_SHOW_FOR_INITIAL_VIEW = 2;
-const getAvailableDates = async (
- startDateString,
- endDateString,
- appointmentType,
- providerNumber
-) => {
+const getScheduleApiResponse = async (startDateString, endDateString, providerNumber) => {
const apiEndDateLimit = sumDateString(startDateString, TIME_SLOTS_CALL_DAYS_LIMIT);
const difference = calcDaysBetweenDates(startDateString, endDateString);
const apiCallsCount = Math.ceil(difference / TIME_SLOTS_CALL_DAYS_LIMIT);
const storeActionConfigs = [];
- const timeSlotsData = {};
- timeSlotsData.days = [];
let apiStartDate = startDateString;
let apiEndDate = endDateString;
for (let i = 1; i <= apiCallsCount; i++) {
- let storeActionConfig;
+ let storeActionConfigInshop;
+ let storeActionConfigMobile;
if (i > 1) {
apiStartDate = sumDateString(apiEndDate, 1);
@@ -133,29 +123,32 @@ const getAvailableDates = async (
}
}
- if (appointmentType === AppointmentTypeStrings.MOBILE) {
- storeActionConfig = {
- storeAction: storeActions.GET_MOBILE_TIME_SLOTS,
- payload: {
- startDate: apiStartDate,
- endDate: apiEndDate,
- },
- };
- } else {
- storeActionConfig = {
- storeAction: storeActions.GET_SHOP_TIME_SLOTS,
- payload: {
- startDate: apiStartDate,
- endDate: apiEndDate,
- shopAppointmentType: appointmentType,
- providerNumber: providerNumber,
- },
- };
+ storeActionConfigMobile = {
+ storeAction: storeActions.GET_MOBILE_TIME_SLOTS,
+ payload: {
+ startDate: apiStartDate,
+ endDate: apiEndDate,
+ },
+ };
+ storeActionConfigInshop = {
+ storeAction: storeActions.GET_SHOP_TIME_SLOTS,
+ payload: {
+ startDate: apiStartDate,
+ endDate: apiEndDate,
+ shopAppointmentType: "InshopOrDropoff",
+ providerNumber: providerNumber,
+ },
+ };
+ if (apiStartDate < apiEndDate) {
+ storeActionConfigs.push(storeActionConfigInshop);
+ storeActionConfigs.push(storeActionConfigMobile);
}
- if (apiStartDate < apiEndDate) storeActionConfigs.push(storeActionConfig);
}
- const timeSlotsResponsesData = {
+ const inshopTimeSlotsData = {
+ days: [],
+ };
+ const mobileTimeSlotsData = {
days: [],
};
@@ -167,39 +160,71 @@ const getAvailableDates = async (
const makeParallelCalls = async () => {
await Promise.all(
- storeActionConfigs.map(async (storeAction) => {
- const timeSlotsResponse = await baseMixin.methods.dispatchStoreActionWithLogging(
- storeAction.storeAction,
- storeAction.payload,
- "schedule",
- false
- );
- timeSlotsResponsesData.estimatedServiceMinutesMinimum =
- timeSlotsResponse.data.estimatedServiceMinutesMinimum;
- timeSlotsResponsesData.estimatedServiceMinutesMaximum =
- timeSlotsResponse.data.estimatedServiceMinutesMaximum;
- timeSlotsResponsesData.days = [
- ...timeSlotsResponsesData.days,
- ...timeSlotsResponse.data.days,
- ];
+ storeActionConfigs.map(async (storeActionConfig) => {
+ if (storeActionConfig.storeAction.indexOf("Mobile") > -1) {
+ const mobileTimeSlotsResponse =
+ await baseMixin.methods.dispatchStoreActionWithLogging(
+ storeActionConfig.storeAction,
+ storeActionConfig.payload,
+ "schedule",
+ false
+ );
+ mobileTimeSlotsData.estimatedServiceMinutesMinimum =
+ mobileTimeSlotsResponse.data.estimatedServiceMinutesMinimum;
+ mobileTimeSlotsData.estimatedServiceMinutesMaximum =
+ mobileTimeSlotsResponse.data.estimatedServiceMinutesMaximum;
+ mobileTimeSlotsData.days = [
+ ...mobileTimeSlotsData.days,
+ ...mobileTimeSlotsResponse.data.days,
+ ];
+ } else {
+ const inshopTimeSlotsResponse =
+ await baseMixin.methods.dispatchStoreActionWithLogging(
+ storeActionConfig.storeAction,
+ storeActionConfig.payload,
+ "schedule",
+ false
+ );
+
+ inshopTimeSlotsData.estimatedServiceMinutesMinimum =
+ inshopTimeSlotsResponse.data.estimatedServiceMinutesMinimum;
+ inshopTimeSlotsData.estimatedServiceMinutesMaximum =
+ inshopTimeSlotsResponse.data.estimatedServiceMinutesMaximum;
+ inshopTimeSlotsData.days = [
+ ...inshopTimeSlotsData.days,
+ ...inshopTimeSlotsResponse.data.days,
+ ];
+ }
})
);
};
return makeParallelCalls().then(() => {
// sort days chronologically
- timeSlotsResponsesData.days.sort(compareDayStrings);
- return timeSlotsResponsesData;
+ inshopTimeSlotsData.days.sort(compareDayStrings);
+ mobileTimeSlotsData.days.sort(compareDayStrings);
+
+ return {
+ inshopTimeSlotsData: inshopTimeSlotsData,
+ mobileTimeSlotsData: mobileTimeSlotsData,
+ };
});
};
+const returnFirstDate = (dates) => {
+ if (dates.days?.length > 0) {
+ return dates.days[0].date;
+ } else {
+ return null;
+ }
+};
+
export default {
name: "schedule",
data() {
return {
selectedDate: this.getSelectedDate(),
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
- selectableDatesData: [],
mobilePremiumAppointmentFee: null,
waitListRequested: null,
displayWaitList: null,
@@ -209,6 +234,9 @@ export default {
pricingByDayBasePrice: null,
pricingByDayUpcharge: null,
showPricingByDay: null,
+ selectableDatesInshop: [],
+ selectableDatesMobile: [],
+ isMobileSelected: false,
};
},
async beforeRouteEnter(to, from, next) {
@@ -230,7 +258,6 @@ export default {
isRecalibrationOnOrder && shouldHideRecalibration
? getItemsWithoutRecalParts(lineItems.glassParts)
: (lineItems.glassParts ?? []);
- const supportingItemsFromStore = lineItems?.supportingItems;
const supportingItemsWithoutFees = baseMixin.methods.filterOutCertainPartTypesOrNumbers(
lineItems.supportingItems,
{
@@ -249,10 +276,14 @@ export default {
const priceString = getAmountDue(lineItemsToBePriced, false); // pass the IncludeTax param as false
const priceStringIntegerRoundedDown = priceString?.split(".")[0]; // same method used as getDisplayPrice() in service-package-radio used on /quote
const pricingByDayBasePrice = parseInt(priceStringIntegerRoundedDown);
+ const isMobileSelected =
+ store.getters.order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE;
let includePricingByDayUpcharge = false;
// Check to see if date should be pre-selected
let preSelectedSlot = await store.getters.order.schedule;
+ let preSelectedDate = preSelectedSlot ? preSelectedSlot.date : null;
+ if (isMobileSelected && preSelectedDate) preSelectedDate += "-mobile";
if (!preSelectedSlot.date || preSelectedSlot?.date?.length < 1) {
preSelectedSlot = null;
} else {
@@ -279,9 +310,9 @@ export default {
const datePickerInitialDataPromise = await datePicker.methods.loadInitialData({
// setup config options for date-picker
selectableDatesSetting: "custom",
- initialViewRowsToShow: 2,
- customSelectableDatesCallback: getAvailableDates,
- preSelectedDate: preSelectedSlot ? preSelectedSlot.date : preSelectedSlot,
+ initialViewRowsToShow: NUMBER_OF_CALENDAR_ROWS_TO_SHOW_FOR_INITIAL_VIEW,
+ getSelectableDatesCallback: getScheduleApiResponse,
+ preSelectedDate: preSelectedDate,
});
// Get pricingByDayUpcharge needed for Pricing By Day
@@ -351,7 +382,10 @@ export default {
vm.setCmsContent(resultMap.cmsContent);
vm.$refs.datePicker.initializeComponent(datePickerInitialData);
vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons);
- vm.selectableDatesData = datePickerInitialData.initialShopTimeSlotsResponse;
+ vm.selectableDatesInshop =
+ datePickerInitialData.initialShopTimeSlotsResponse.inshopTimeSlotsData;
+ vm.selectableDatesMobile =
+ datePickerInitialData.initialShopTimeSlotsResponse.mobileTimeSlotsData;
vm.mobilePremiumAppointmentFee = resultMap.premiumFeeWithPrice
? resultMap.premiumFeeWithPrice[0]
: null;
@@ -363,21 +397,33 @@ export default {
vm.pricingByDayBasePrice = pricingByDayBasePrice;
vm.pricingByDayUpcharge = pricingByDayUpcharge;
vm.showPricingByDay = showPricingByDay;
+ vm.isMobileSelected = isMobileSelected;
});
},
mounted() {
this.$nextTick(() => {
- const selectableDatesData = this.selectableDatesData;
- // if no date selected on load
if (!this.selectedDate) {
- if (selectableDatesData?.days?.length > 0) {
- this.selectedDate = selectableDatesData.days[0].date;
+ // 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((moreSelectableDatesData) => {
- this.selectableDatesData = moreSelectableDatesData;
- if (selectableDatesData?.days?.length > 0) {
- this.selectedDate = selectableDatesData.days[0].date;
+ 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();
@@ -396,27 +442,46 @@ export default {
return this.splitCopyOnCMSPlaceHolder(this.ChangeShopLinkText);
},
appointmentType() {
- return this.$store.getters.order.serviceLocation.appointmentType;
+ // TODO - THIS HAS TO BE CHANGED. APPOINTMENT TYPE CAN NOW CHANGE ON THE FLY, NOT FROM STORE ANYMORE.
+ // return this.$store.getters.order.serviceLocation.appointmentType;
+ if (this.isMobileSelected) {
+ return "Mobile";
+ } else {
+ return "Inshop";
+ }
+ },
+ getServiceMinutesMin() {
+ return this.isMobileSelected
+ ? this.selectableDatesMobile.estimatedServiceMinutesMinimum
+ : this.selectableDatesInshop.estimatedServiceMinutesMinimum;
+ },
+ getServiceMinutesMax() {
+ return this.isMobileSelected
+ ? this.selectableDatesMobile.estimatedServiceMinutesMaximum
+ : this.selectableDatesInshop.estimatedServiceMinutesMaximum;
},
timeSlotsForSelectedDate() {
if (!this.selectedDate) return null;
+ const selectedDate = this.selectedDate;
- return this.selectableDatesData.days?.find(
- (selectableDate) => selectableDate.date === this.selectedDate
- );
+ if (this.isMobileSelected) {
+ const selectedDateString = selectedDate.replace("-mobile", "");
+ return this.selectableDatesMobile.days?.find(
+ (selectableDate) => selectableDate.date === selectedDateString
+ );
+ } else {
+ return this.selectableDatesInshop.days?.find(
+ (selectableDate) => selectableDate.date === this.selectedDate
+ );
+ }
},
},
methods: {
splitCopyOnCMSPlaceHolder,
arePagePrerequisitesValid() {
- const serviceLocation = store.getters.order.serviceLocation;
+ const serviceLocation = store.getters.order.serviceLocation; // TODO - MAY NEED TO REMOVE THIS AS A PREREQ
- const serviceLocationPreReqs =
- serviceLocation.zipCode &&
- serviceLocation.zipCodeCtu &&
- serviceLocation.appointmentType &&
- (serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE ||
- serviceLocation.provider.providerNumber);
+ const serviceLocationPreReqs = serviceLocation.zipCode && serviceLocation.zipCodeCtu; // TODO - MAY NEED TO ADD IN SERVICE LOCATION PREREQS HERE TOO
const paymentInfo = store.getters.payment.isInsurance !== null;
@@ -441,26 +506,38 @@ export default {
}
return preReqResult;
},
- async getAvailableDatesMethod(startDate, endDate) {
- const newShopTimeSlots = await getAvailableDates(
+ async getMoreScheduleData(startDate, endDate) {
+ // called only when "View more dates" is clicked; not on initial page load
+ const moreShopTimeSlots = await getScheduleApiResponse(
startDate,
endDate,
- this.appointmentType,
this.$store.getters.order.serviceLocation.provider.providerNumber
);
// ADD API CALL RESULTS TO EXISTING DATE DATA
- this.selectableDatesData.days = this.selectableDatesData.days.concat(
- newShopTimeSlots.days
+ this.selectableDatesInshop.days = this.selectableDatesInshop.days.concat(
+ moreShopTimeSlots.inshopTimeSlotsData.days
);
- return newShopTimeSlots;
+ this.selectableDatesMobile.days = this.selectableDatesMobile.days.concat(
+ moreShopTimeSlots.mobileTimeSlotsData.days
+ );
+
+ return moreShopTimeSlots;
},
- getAvailableDates,
+ getScheduleApiResponse,
getServiceZipCtuCodeFromStore() {
return store.getters.order.serviceLocation.zipCodeCtu;
},
getSelectedDate() {
- return store.getters.order.schedule.date;
+ let isMobileSelected = this.isMobileSelected;
+ if (isMobileSelected === undefined) {
+ isMobileSelected =
+ store.getters.order.serviceLocation.appointmentType ===
+ AppointmentTypeStrings.MOBILE;
+ }
+ let storedDate = store.getters.order.schedule.date;
+ if (isMobileSelected) storedDate += "-mobile";
+ return storedDate;
},
getSelectedTimeSlotInfo() {
const supportingItems = this.getSupportingItems();
@@ -513,8 +590,7 @@ export default {
},
convertSelectedDateToShortMonthAndDay(selectedDate) {
// This conversion ensures we don't get get GMT induced date changes
- const dateObject = convertDateStringToDate(selectedDate);
- // Ex: April 25
+ const dateObject = convertDateStringToDate(selectedDate.replace("-mobile", ""));
return dateObject.toLocaleDateString("en-us", { month: "short", day: "numeric" });
},
getDisplayTextForMilitaryTime(militaryTimeInput, shouldTrimMinutesIfEmpty = false) {
@@ -523,10 +599,7 @@ export default {
const minutes = militaryTimeInput.split(":")[1];
const meridianNotation = hours > 11 ? "PM" : "AM";
- if (hours > 12) {
- hours -= 12;
- }
-
+ if (hours > 12) hours -= 12;
if (shouldTrimMinutesIfEmpty && minutes === "00") {
return `${hours} ${meridianNotation}`;
} else {
@@ -540,6 +613,7 @@ export default {
);
},
forwardButtonAction() {
+ // TODO - WILL NEED TO SAVE THE APPOINTMENT TYPE ALSO (AND ANYTHING ELSE THAT SERVICE-LOCATION DID TOO)
this.updateSupportingItems();
if (this.displayWaitList) {
@@ -555,7 +629,9 @@ export default {
gaLabel = `${status}_${type}`;
const currentDate = new Date();
- const dateString = this.selectableDatesData.days[0].date;
+ const dateString = this.isMobileSelected
+ ? this.selectableDatesMobile.days[0].date
+ : this.selectableDatesInshop.days[0].date;
const [year, month, day] = dateString.split("-").map(Number);
const appointmentDate = new Date(year, month - 1, day);
const timeDifference = appointmentDate - currentDate;
@@ -572,11 +648,14 @@ export default {
store.getters.order.serviceLocation.zipCodeCtu;
this.pushEventToGA("waitlist", "add_to_waitlist_check_box_status", gaLabel, true);
}
+
if (this.selectedTimeSlotInfo.timeSlot.jobMinMinutes == null) {
- this.selectedTimeSlotInfo.timeSlot.jobMinMinutes =
- this.selectableDatesData?.estimatedServiceMinutesMinimum?.toString();
- this.selectedTimeSlotInfo.timeSlot.jobMaxMinutes =
- this.selectableDatesData?.estimatedServiceMinutesMaximum?.toString();
+ 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(
@@ -604,9 +683,12 @@ export default {
experimentSettings.DISPLAY_WAITLIST,
"true"
) &&
- this.selectableDatesData.days[0]
+ this.selectableDatesInshop.days[0] && // TODO - ASK BUSINESS... WHAT LOGIC SHOULD WE USE HERE FOR WHEN TO DISPLAY THE WAIT LIST?
+ this.selectableDatesMobile.days[0]
) {
- const dateString = this.selectableDatesData.days[0].date;
+ 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();
diff --git a/src/layouts/service-location/service-location.spec.js b/src/layouts/service-location/service-location.spec.js
index 58187496e..551e15ae0 100644
--- a/src/layouts/service-location/service-location.spec.js
+++ b/src/layouts/service-location/service-location.spec.js
@@ -314,57 +314,6 @@ describe("service-location.vue", () => {
expect(wrapper.vm.serviceZipCodeQuestion).toStrictEqual(newServiceZipCodeQuestion);
});
- test("resets mobile location when service zip code is updated", () => {
- // Arrange
- const { wrapper } = setupMocks({});
-
- const mobileLocationQuestionsComponent = wrapper.findComponent({
- ref: "mobileLocationQuestions",
- });
- mobileLocationQuestionsComponent.resetComponent = jest.fn();
-
- const serviceZipCodeComponent = wrapper.findComponent({
- ref: "serviceZipCodeQuestion",
- });
- serviceZipCodeComponent.resetMobileFeePart = jest.fn();
-
- const newServiceZipCodeQuestion = {
- zipCode: "61606",
- state: "IL",
- zipCodeCtu: "01526",
- };
-
- const mobileLocationQuestions = {
- addressQuestions: {
- streetAddress: "5555 Sulgrave Dr",
- apartmentNumberOrBusinessName: "Apt 1",
- city: "New Albany",
- state: "OH",
- zipCode: "43054",
- },
- isVehicleProtected: true,
- };
- wrapper.vm.mobileLocationQuestions = mobileLocationQuestions;
-
- const newMobileLocationQuestions = {
- addressQuestions: {
- streetAddress: "",
- apartmentNumberOrBusinessName: "",
- city: "",
- state: "IL",
- zipCode: "61606",
- },
- isVehicleProtected: null,
- isMobileSelected: false,
- };
-
- // Act
- serviceZipCodeComponent.vm.$emit("update:modelValue", newServiceZipCodeQuestion);
-
- // Assert
- expect(wrapper.vm.mobileLocationQuestions).toStrictEqual(newMobileLocationQuestions);
- });
-
test("resets appointment type selection when service zip code is updated by service zip modal", () => {
// Arrange
const { wrapper } = setupMocks({});
@@ -424,190 +373,6 @@ describe("service-location.vue", () => {
});
});
- describe("updating mobile location", () => {
- test("updates the page model after providing the mobile location", async () => {
- // Arrange
- const { wrapper } = setupMocks({});
-
- await wrapper.setData({
- shopProviderData: {
- mobileProviderNumber: "001820",
- },
- selectedAppointmentType: "Mobile",
- providerData: { mobileProviderNumber: "01820" },
- isGlassServiceableMobile: true,
- });
-
- const mobileLocationQuestionsComponent = wrapper.findComponent({
- ref: "mobileLocationQuestions",
- });
- mobileLocationQuestionsComponent.resetComponent = jest.fn();
-
- const serviceZipCodeComponent = wrapper.findComponent({
- ref: "serviceZipCodeQuestion",
- });
- serviceZipCodeComponent.resetMobileFeePart = jest.fn();
- wrapper.vm.forwardButtonAction = jest.fn();
-
- const mobileLocationQuestions = {
- addressQuestions: {
- streetAddress: "",
- apartmentNumberOrBusinessName: "",
- city: "",
- state: "",
- zipCode: "",
- },
- isVehicleProtected: null,
- };
- wrapper.vm.mobileLocationQuestions = mobileLocationQuestions;
- const newMobileLocationQuestions = {
- addressQuestions: {
- streetAddress: "5555 Sulgrave Dr",
- apartmentNumberOrBusinessName: "Apt 1",
- city: "New Albany",
- state: "OH",
- zipCode: "43054",
- },
- isVehicleProtected: true,
- isMobileSelected: true,
- };
-
- // Act
-
- // Trigger the event
- await mobileLocationQuestionsComponent.vm.$emit(
- "setMobileLocation",
- newMobileLocationQuestions
- );
-
- await wrapper.vm.$nextTick(); // Wait for DOM updates
-
- // Assert
- expect(wrapper.vm.mobileLocationQuestions).toStrictEqual(newMobileLocationQuestions);
- });
-
- test("resets service zip code when mobile location is updated", async () => {
- // Arrange
- const { wrapper } = setupMocks({});
-
- await wrapper.setData({
- shopProviderData: {
- mobileProviderNumber: "001820",
- },
- selectedAppointmentType: "Mobile",
- providerData: { mobileProviderNumber: "01820" },
- isGlassServiceableMobile: true,
- });
-
- const mobileLocationQuestionsComponent = wrapper.findComponent({
- ref: "mobileLocationQuestions",
- });
- mobileLocationQuestionsComponent.resetComponent = jest.fn();
-
- const serviceZipCodeComponent = wrapper.findComponent({
- ref: "serviceZipCodeQuestion",
- });
- serviceZipCodeComponent.resetMobileFeePart = jest.fn();
- wrapper.vm.forwardButtonAction = jest.fn();
- const mobileLocationQuestions = {
- addressQuestions: {
- streetAddress: "",
- apartmentNumberOrBusinessName: "",
- city: "",
- state: "",
- zipCode: "",
- },
- isVehicleProtected: null,
- isMobileSelectedAndModalClosed: false,
- mobileFeePart: null,
- };
- wrapper.vm.mobileLocationQuestions = mobileLocationQuestions;
-
- const newMobileLocationQuestions = {
- addressQuestions: {
- streetAddress: "5555 Rustic Dr",
- apartmentNumberOrBusinessName: "Apt 1",
- city: "Westerville",
- state: "OH",
- zipCode: "43081",
- },
- isVehicleProtected: true,
- isMobileSelectedAndModalClosed: true,
- mobileFeePart: null,
- };
-
- wrapper.vm.serviceZipCodeQuestion = {
- zipCode: "61606",
- state: "IL",
- zipCodeCtu: "01526",
- };
-
- const newServiceZipCodeInfo = {
- state: "OH",
- zipCode: "43081",
- zipCodeCtu: "01526",
- };
-
- wrapper.vm.closeModalAction = jest.fn();
- // Act
-
- // Trigger the event
- await mobileLocationQuestionsComponent.vm.$emit(
- "setMobileLocation",
- newMobileLocationQuestions
- );
-
- await wrapper.vm.$nextTick(); // Wait for DOM updates
-
- // Assert
- expect(wrapper.vm.serviceZipCodeQuestion).toStrictEqual(newServiceZipCodeInfo);
- });
-
- test("does not reset appointment type selection when service zip code is updated by mobile location modal when Mobile is selected", async () => {
- // Arrange
- const { wrapper } = setupMocks({});
-
- await wrapper.setData({
- shopProviderData: {
- mobileProviderNumber: "001820",
- },
- selectedAppointmentType: "Mobile",
- providerData: { mobileProviderNumber: "01820" },
- });
-
- const mobileLocationQuestionsComponent = wrapper.findComponent({
- ref: "mobileLocationQuestions",
- });
- mobileLocationQuestionsComponent.resetComponent = jest.fn();
- wrapper.vm.forwardButtonAction = jest.fn();
- wrapper.vm.isServiceableMobile = true;
-
- const serviceZipCodeComponent = wrapper.findComponent({
- ref: "serviceZipCodeQuestion",
- });
- serviceZipCodeComponent.resetMobileFeePart = jest.fn();
-
- const mobileLocationQuestions = {
- addressQuestions: {
- streetAddress: "555 Some Street",
- apartmentNumberOrBusinessName: "",
- city: "Westerville",
- state: "OH",
- zipCode: "43081",
- },
- isVehicleProtected: "YesAnswer",
- isMobileSelectedAndModalClosed: true,
- };
-
- wrapper.vm.closeModalAction = jest.fn();
- // Act
- mobileLocationQuestionsComponent.vm.$emit("setMobileLocation", mobileLocationQuestions);
-
- // Assert
- expect(wrapper.vm.selectedAppointmentType).toStrictEqual("Mobile");
- });
- });
-
describe("serviceability logic", () => {
describe("should be logical AND when recalibration is defined.", () => {
test("T & T => T", async () => {
@@ -1434,8 +1199,5 @@ function setupMocks({ mountOptionsMockData = {} }) {
const mountOptions = getMountOptions(mountOptionsMockData);
const wrapper = shallowMount(serviceLocation, mountOptions);
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
- wrapper.vm.$refs.mobileLocationQuestions.isMobileAddressComplete = jest.fn();
- wrapper.vm.$refs.mobileLocationQuestions.resetAlerts = jest.fn();
- wrapper.vm.$refs.mobileLocationQuestions.openModal = jest.fn();
return { wrapper, apiPromise };
}
diff --git a/src/layouts/service-location/service-location.vue b/src/layouts/service-location/service-location.vue
index 6893ad9b4..ccb2c7866 100644
--- a/src/layouts/service-location/service-location.vue
+++ b/src/layouts/service-location/service-location.vue
@@ -89,24 +89,14 @@
-
-
+
+
+
{
- if (this.isServiceableMobile) {
- this.shopProviderData = result.data;
- this.selectedProvider = new Provider(
- this.shopProviderData.mobileProviderNumber
- );
- }
- });
- }
- if (this.isServiceableMobile) {
- this.setMobileLocation(newValue);
- this.setMobileLocationInValid(false);
- } else {
- await getZipCodeData(newZipCode).then((zipCodeData) => {
- this.serviceZipCodeQuestion = {
- state: zipCodeData.state,
- zipCode: newZipCode,
- zipCodeCtu: zipCodeData.zipCodeCtu,
- };
- });
- }
- },
getCarIdfromStore() {
return store.getters.vehicle.carId;
},
@@ -590,15 +556,6 @@ export default {
this.isRecalibrationServiceableMobile =
serviceabilityDetails.isRecalibrationServiceableMobile;
},
- setMobileLocation(mobileLocation) {
- this.streetAddress = mobileLocation.addressQuestions.streetAddress;
- this.apartmentNumberOrBusinessName =
- mobileLocation.addressQuestions.apartmentNumberOrBusinessName;
- this.city = mobileLocation.addressQuestions.city;
- this.state = mobileLocation.addressQuestions.state;
- this.zipCode = mobileLocation.addressQuestions.zipCode;
- this.isVehicleProtected = mobileLocation.isVehicleProtected;
- },
async reloadShopData(zipCode) {
await this.$refs.shopQuestion.reloadShopData(zipCode);
},
@@ -755,13 +712,49 @@ export default {
this.updateAndSaveSupportingItems();
this.updateAndSaveIsMSRFeeApplicable();
- this.$router.navigateWithSaving(
- this.navigationScenarios.CLICKED_FORWARD,
- this.pageName
+
+ //mocked schedule and should be clear when implement schedule page.
+ this.dispatchStoreAction(
+ this.storeActions.SAVE_SCHEDULE,
+ this.getTimeSlotInfo(),
+ false
);
+
+ if (this.selectedAppointmentType == AppointmentTypeStrings.MOBILE) {
+ //navigate to mobile details page
+ this.$router.navigateWithSaving(
+ this.navigationScenarios.CLICKED_FORWARD_WITH_MOBILE_SERVICE,
+ this.pageName
+ );
+ } else {
+ this.$router.navigateWithSaving(
+ this.navigationScenarios.CLICKED_FORWARD,
+ this.pageName
+ );
+ }
},
- setMobileLocationInValid(isMobileLocationInValid) {
- this.isMobileAddressValid = !isMobileLocationInValid;
+ getTimeSlotInfo() {
+ // Get the current date
+ const currentDate = new Date();
+
+ // Add 10 days to the current date
+ currentDate.setDate(currentDate.getDate() + 10);
+
+ // Format the date as yyyy-mm-dd
+ const year = currentDate.getFullYear();
+ const month = String(currentDate.getMonth() + 1).padStart(2, "0");
+ const day = String(currentDate.getDate()).padStart(2, "0");
+
+ const formattedDate = `${year}-${month}-${day}`;
+
+ return {
+ date: formattedDate,
+ endTime: "12:00",
+ jobMaxMinutes: "180",
+ jobMinMinutes: "120",
+ routeCode: "3357I-03357-M-I*20988*AM",
+ startTime: "08:00",
+ };
},
},
watch: {
@@ -787,16 +780,6 @@ export default {
this.selectedProvider = new Provider(
this.shopProviderData.mobileProviderNumber
);
- if (this.zipCode == this.getServiceZipCodeFromStore()) {
- //restore mobile address from store in case user make changes to address but not commit it.
- this.streetAddress = this.getServiceAddressFromStore();
- this.apartmentNumberOrBusinessName = this.getServiceAddress2FromStore();
- this.city = this.getServiceCityFromStore();
- this.isVehicleProtected = this.getIsVehicleProtectedFromStore();
- } else {
- this.resetMobileLocation();
- }
- this.$refs.mobileLocationQuestions.resetAlerts();
} else {
this.selectedProvider = new Provider();
}
@@ -807,7 +790,6 @@ export default {
alert,
serviceZipModalQuestion,
appointmentTypeQuestion,
- mobileLocationModalQuestions,
funnelHeader,
navbar,
funnelSubHeader,
@@ -815,6 +797,7 @@ export default {
loadingModal,
contentGroupModal,
shopQuestion,
+ textBlock,
},
};
diff --git a/src/router/constants/navigation-scenarios.js b/src/router/constants/navigation-scenarios.js
index a6e117948..b660afca3 100644
--- a/src/router/constants/navigation-scenarios.js
+++ b/src/router/constants/navigation-scenarios.js
@@ -49,6 +49,12 @@ const navigationScenarios = {
// Insurance-Company
CLICKED_PAY_ON_MY_OWN: "CLICKED_PAY_ON_MY_OWN",
+ //Service-Location
+ CLICKED_FORWARD_WITH_MOBILE_SERVICE: "CLICKED_FORWARD_WITH_MOBILE_SERVICE",
+
+ //Customer-details
+ CLICKED_BACK_WITH_MOBILE_SERVICE: "CLICKED_BACK_WITH_MOBILE_SERVICE",
+
// Schedule
CLICKED_CHANGE_LOCATION: "CLICKED_CHANGE_LOCATION",
diff --git a/src/router/constants/routes.js b/src/router/constants/routes.js
index d03954867..7e9a3a793 100644
--- a/src/router/constants/routes.js
+++ b/src/router/constants/routes.js
@@ -68,6 +68,10 @@ export const routeData = {
name: "schedule",
path: "/schedule",
},
+ MOBILE_DETAILS: {
+ name: "mobile-details",
+ path: "/mobile-details",
+ },
CUSTOMER_DETAILS: {
name: "customer-details",
path: "/customer-details",
diff --git a/src/router/constants/routing-table.js b/src/router/constants/routing-table.js
index 4b7286741..4a07bcfaf 100644
--- a/src/router/constants/routing-table.js
+++ b/src/router/constants/routing-table.js
@@ -460,6 +460,10 @@ const routingTable = function () {
scenario: navigationScenarios.CLICKED_FORWARD,
destinationPageData: routeData.SCHEDULE,
},
+ {
+ scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOBILE_SERVICE,
+ destinationPageData: routeData.MOBILE_DETAILS,
+ },
],
},
{
@@ -479,6 +483,19 @@ const routingTable = function () {
},
],
},
+ {
+ pageName: routeData.MOBILE_DETAILS.name,
+ maps: [
+ {
+ scenario: navigationScenarios.CLICKED_BACK,
+ destinationPageData: routeData.SERVICE_LOCATION,
+ },
+ {
+ scenario: navigationScenarios.CLICKED_FORWARD,
+ destinationPageData: routeData.CUSTOMER_DETAILS,
+ },
+ ],
+ },
{
pageName: routeData.CUSTOMER_DETAILS.name,
maps: [
@@ -486,6 +503,10 @@ const routingTable = function () {
scenario: navigationScenarios.CLICKED_BACK,
destinationPageData: routeData.SCHEDULE,
},
+ {
+ scenario: navigationScenarios.CLICKED_BACK_WITH_MOBILE_SERVICE,
+ destinationPageData: routeData.MOBILE_DETAILS,
+ },
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationPageData: routeData.PAYMENT_METHOD,
diff --git a/src/router/methods/routes.js b/src/router/methods/routes.js
index c029ce2cb..01f4f8a58 100644
--- a/src/router/methods/routes.js
+++ b/src/router/methods/routes.js
@@ -37,6 +37,7 @@ export const routes = [
createRoute(routeData.PAYMENT_PIA_RETURN),
createRoute(routeData.CONFIRMATION),
createRoute(routeData.RETURN_USER),
+ createRoute(routeData.MOBILE_DETAILS),
// Virtual pages (resolve to a non-virtual page.)
createVirtualRoute(routeData.LANDING, landingBeforeEnter),
diff --git a/src/store/index.js b/src/store/index.js
index 2cfe39ead..83d8d99af 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -425,6 +425,12 @@ export const mutations = {
},
};
},
+ updateMobileDetails(state, mobileDetails) {
+ state.order.serviceLocation.address = mobileDetails.address;
+ state.order.serviceLocation.address2 = mobileDetails.address2;
+ state.order.serviceLocation.city = mobileDetails.city;
+ state.order.serviceLocation.isVehicleProtected = mobileDetails.isVehicleProtected;
+ },
updateServiceLocationTechNotes(state, techNotes) {
state.order.serviceLocation.techNotes = techNotes;
},
@@ -3238,6 +3244,9 @@ export const actions = {
context.commit(storeMutations.UPDATE_SERVICE_LOCATION, serviceLocationInfo);
},
+ saveMobileDetails(context, mobileDetails) {
+ context.commit(storeMutations.UPDATE_MOBILE_DETAILS, mobileDetails);
+ },
saveServiceLocationTechNotes(context, techNotesInfo) {
context.commit(storeMutations.UPDATE_SERVICE_LOCATION_TECH_NOTES, techNotesInfo);