diff --git a/src/assets/img/icons/Google.svg b/src/assets/img/icons/Google.svg new file mode 100644 index 000000000..606351ec9 --- /dev/null +++ b/src/assets/img/icons/Google.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/assets/img/icons/Outlook.com.svg b/src/assets/img/icons/Outlook.com.svg new file mode 100644 index 000000000..467b585ac --- /dev/null +++ b/src/assets/img/icons/Outlook.com.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/assets/img/icons/Outlook.svg b/src/assets/img/icons/Outlook.svg new file mode 100644 index 000000000..f974fb13a --- /dev/null +++ b/src/assets/img/icons/Outlook.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/assets/img/icons/Yahoo.svg b/src/assets/img/icons/Yahoo.svg new file mode 100644 index 000000000..0206eccbb --- /dev/null +++ b/src/assets/img/icons/Yahoo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/assets/img/icons/iCal.svg b/src/assets/img/icons/iCal.svg new file mode 100644 index 000000000..399bcdea0 --- /dev/null +++ b/src/assets/img/icons/iCal.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/constants/calendar-options.js b/src/constants/calendar-options.js new file mode 100644 index 000000000..c87a85b18 --- /dev/null +++ b/src/constants/calendar-options.js @@ -0,0 +1,8 @@ +const calendarOptions = { + ICAL: "iCal", + GOOGLE: "Google", + OUTLOOK: "Outlook", + OUTLOOKCOM: "Outlook.com", + YAHOO: "Yahoo", +}; +export { calendarOptions }; diff --git a/src/constants/calendar-status.js b/src/constants/calendar-status.js new file mode 100644 index 000000000..38cf23467 --- /dev/null +++ b/src/constants/calendar-status.js @@ -0,0 +1,7 @@ +const calendarStatus = { + FREE: "Free", + BUSY: "Busy", + TENTATIVE: "Tentative", + OUT_OF_THE_OFFICE: "OutOfTheOffice", +}; +export { calendarStatus }; diff --git a/src/helpers/add-to-calendar-helper.js b/src/helpers/add-to-calendar-helper.js new file mode 100644 index 000000000..36c700cb3 --- /dev/null +++ b/src/helpers/add-to-calendar-helper.js @@ -0,0 +1,76 @@ +import { calendarStatus } from "@/constants/calendar-status"; +import { getDateFormat } from "@/helpers/date-helper"; +export function getCalendarFile(calFile) { + const dateFormat = "yyyyMMddTHHmmss"; + var calEvent = []; + calEvent.push("BEGIN:VCALENDAR"); + calEvent.push("VERSION:2.0"); + calEvent.push("BEGIN:VEVENT"); + calEvent.push("DTSTAMP:" + getDateFormat(new Date(), dateFormat)); + calEvent.push("UID:" + calFile.UniqueId + "@safelite.com"); + calEvent.push("PRODID:noreply@safelite.com"); + switch (calFile.Status) { + case calendarStatus.BUSY: + calEvent.push("X-MICROSOFT-CDO-BUSYSTATUS:BUSY"); + break; + case calendarStatus.FREE: + calEvent.push("TRANSP:TRANSPARENT"); + break; + case calendarStatus.TENTATIVE: + calEvent.push("STATUS:TENTATIVE"); + break; + case calendarStatus.OUT_OF_THE_OFFICE: + calEvent.push("X-MICROSOFT-CDO-BUSYSTATUS:OOF"); + break; + default: + //throw new Exception("Invalid CalendarStatus"); + } + if (calFile.AllDayEvent) { + calEvent.push("DTSTART;VALUE=DATE:" + getDateFormat(calFile.StartDate, dateFormat)); + calEvent.push("DTEND;;VALUE=DATE:" + getDateFormat(calFile.EndDate, dateFormat)); + } else { + calEvent.push("DTSTART:" + getDateFormat(calFile.StartDate, dateFormat)); + calEvent.push("DTEND:" + getDateFormat(calFile.EndDate, dateFormat)); + } + + calEvent.push("SUMMARY:" + ToCalendarFileString(calFile.Subject, " ")); + if (calFile.Location != null && calFile.Location != "") { + calEvent.push("LOCATION:" + ToCalendarFileString(calFile.Location, " ")); + } + if (calFile.Body != null && calFile.Body != "") { + if (calFile.IsHTML) { + calEvent.push("DESCRIPTION:" + ToCalendarFileString(calFile.Body)); + calEvent.push( + "X-ALT-DESC;FMTTYPE=text/html:" + + '' + + "" + + '

' + + "" + + ToCalendarFileString(calFile.Body) + + "" + ); + } else { + calEvent.push("DESCRIPTION:" + ToCalendarFileString(calFile.Body)); + } + } + calEvent.push("END:VEVENT"); + calEvent.push("END:VCALENDAR"); + return calEvent.join("\r\n"); +} + +function ToCalendarFileString(str, replacementArg = "

") { + return str.replace("\r\n", replacementArg); +} + +export function download(filename, fileBody) { + var element = document.createElement("a"); + element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(fileBody)); + element.setAttribute("download", filename); + + element.style.display = "none"; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); +} diff --git a/src/helpers/date-helper.js b/src/helpers/date-helper.js index 48b3122ab..d1c9f968b 100644 --- a/src/helpers/date-helper.js +++ b/src/helpers/date-helper.js @@ -8,7 +8,6 @@ export function getDateDifferenceInDays(startDate, endDate) { // To calculate the no. of days between two dates return Difference_In_Time / (1000 * 3600 * 24); } - export function getFullDayName(date) { if (date instanceof Date !== true) return; const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; @@ -62,3 +61,67 @@ export function get12HourTimeMobileFormat(time) { } return time.join(""); // return adjusted time or original string } +export function getDateFormat(date, format) { + if (date instanceof Date !== true) return; + const year = date.getFullYear(); + const month = ("0" + (date.getMonth() + 1)).slice(-2); + const day = ("0" + date.getDate()).slice(-2); + const hour = ("0" + date.getHours()).slice(-2); + const minute = ("0" + date.getMinutes()).slice(-2); + const second = ("0" + date.getSeconds()).slice(-2); + return format + .replace("yyyy", year) + .replace("MM", month) + .replace("dd", day) + .replace("hh", hour) + .replace("HH", hour) + .replace("mm", minute) + .replace("ss", second); +} +export function calculateDuration(startDate, endDate) { + if (startDate instanceof Date !== true) return; + if (endDate instanceof Date !== true) return; + return convertMsToTime(endDate - startDate); +} +export function convertMsToTime(milliseconds) { + let seconds = Math.floor(milliseconds / 1000); + let minutes = Math.floor(seconds / 60); + let hours = Math.floor(minutes / 60); + + seconds = seconds % 60; + minutes = minutes % 60; + //commenting to get 24 time format + //hours = hours % 24; + + return `${padTo2Digits(hours)}${padTo2Digits(minutes)}`; +} +export function padTo2Digits(time) { + return ("0" + time).slice(-2); +} +export function combineDateAndTime(date, time) { + var newDate = new Date(date); + var timeParts = time.split(":"); + newDate.setHours(timeParts[0]); + newDate.setMinutes(timeParts[1]); + return newDate; +} +export function addMinutes(date, minutes) { + return new Date(date.getTime() + minutes * 60000); +} +export function shortTimeString(date) { + if (date instanceof Date !== true) return; + const hour = ("0" + date.getHours()).slice(-2); + const minute = ("0" + date.getMinutes()).slice(-2); + //const second = ("0" + date.getSeconds()).slice(-2); + let time = hour + ":" + minute; + // Check correct time format and split into components + time = time.toString().match(/^([01]\d|2[0-3])(:)([0-5]\d)?$/) || [time]; + + if (time.length > 1) { + // If time format correct + time = time.slice(1); // Remove full string match value + time[5] = +time[0] < 12 ? " AM" : " PM"; // Set AM/PM + time[0] = +time[0] % 12 || 12; // Adjust hours + } + return time.join(""); // return adjusted time or original string +} diff --git a/src/layouts/add-to-calendar/add-to-calendar.spec.js b/src/layouts/add-to-calendar/add-to-calendar.spec.js new file mode 100644 index 000000000..1bee62c2c --- /dev/null +++ b/src/layouts/add-to-calendar/add-to-calendar.spec.js @@ -0,0 +1,94 @@ +// Components +import addToCalendar from "@/layouts/add-to-calendar/add-to-calendar.vue"; + +// Supporting Files +import { shallowMount } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; +import store from "@/store"; + +beforeEach(() => { + jest.restoreAllMocks(); + jest.clearAllMocks(); + store.getters = { + order: { + schedule: { + date: "2019-01-01", + startTime: "09:00", + endTime: "10:00", + routeCode: "000", + }, + lineItems: { + glassParts: [ + { + partNumber: "ABC123", + }, + ], + supportingItems: [], + }, + serviceLocation: { + address: "", + address2: null, + city: "", + state: "AZ", + appointmentType: "Inshop", + zipCode: "12345", + zipCodeCtu: "01234", + provider: { + providerNumber: "123", + address: { + streetAddress: "test1", + city: "test", + state: "AZ", + zipCode: "12345", + zipCodeCtu: "01234", + }, + }, + }, + damage: { + isRepair: false, + }, + referralNumber: "1234567", + }, + payment: { + isInsurance: true, + }, + lineItems: { + glassParts: [], + supportingItems: [], + }, + }; +}); +afterEach(() => { + store.getters = {}; + jest.restoreAllMocks(); + jest.clearAllMocks(); +}); + +describe("Add-to-calendar methods...", () => { + test("Add-to-calendar should trigger openModal method", () => { + //Arrange + const { wrapper } = setupMocks({}); + + //Act + wrapper.vm.openCalendarModal(); + + //Assert + expect(wrapper.vm.$refs.calendarModalQuestion.openModal).toBeCalled(); + }); +}); + +function setupMocks({ customMountOptions }) { + const mountOptions = getMountOptions({ + ...customMountOptions, + }); + + const wrapper = shallowMount(addToCalendar, mountOptions); + + //wrapper.vm.setCmsContent = jest.fn(); + //wrapper.vm.$refs.datePicker.initializeComponent = jest.fn(); + //wrapper.vm.$refs.locationAlerts.initializeComponent = jest.fn(); + //wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn(); + wrapper.vm.$refs.calendarModalQuestion.openModal = jest.fn(); + + return { wrapper }; +} diff --git a/src/layouts/add-to-calendar/add-to-calendar.vue b/src/layouts/add-to-calendar/add-to-calendar.vue new file mode 100644 index 000000000..b0306b5d0 --- /dev/null +++ b/src/layouts/add-to-calendar/add-to-calendar.vue @@ -0,0 +1,300 @@ + + + diff --git a/src/layouts/add-to-calendar/calendar-modal-question/calendar-modal-list-button/calendar-modal-list-button.vue b/src/layouts/add-to-calendar/calendar-modal-question/calendar-modal-list-button/calendar-modal-list-button.vue new file mode 100644 index 000000000..7f4fe2801 --- /dev/null +++ b/src/layouts/add-to-calendar/calendar-modal-question/calendar-modal-list-button/calendar-modal-list-button.vue @@ -0,0 +1,79 @@ + + + diff --git a/src/layouts/add-to-calendar/calendar-modal-question/calendar-modal-question.spec.js b/src/layouts/add-to-calendar/calendar-modal-question/calendar-modal-question.spec.js new file mode 100644 index 000000000..d28c2442f --- /dev/null +++ b/src/layouts/add-to-calendar/calendar-modal-question/calendar-modal-question.spec.js @@ -0,0 +1,53 @@ +import { shallowMount } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; +import calendarModalQuestion from "@/layouts/add-to-calendar/calendar-modal-question/calendar-modal-question.vue"; + +describe("calendar-modal-question.vue", () => { + test("When selected calendar option is changed, a correctly selectedCalendarOption should be emitted", async () => { + //Arrange + const url = "iCal url"; + const name = "iCal"; + const icon = "iCal.svg"; + const expectedEmit = [ + [ + { + url: url, + name: name, + icon: icon, + }, + ], + ]; + + const { wrapper } = setupMocks({ + customMountOptions: { + propsData: { + calendarOptionsData: [ + { + url: url, + name: name, + icon: icon, + }, + ], + }, + }, + }); + + wrapper.vm.$refs.calendarOptions.closeModal = jest.fn(); + wrapper.vm.selectedCalendarOption = name; + + //Act + wrapper.vm.setSelectedCalendarOption(); + + //Assert + expect(wrapper.emitted()["update:modelValue"]).toEqual(expectedEmit); + }); +}); + +function setupMocks({ customMountOptions }) { + const mountOptions = getMountOptions({ + ...customMountOptions, + }); + + const wrapper = shallowMount(calendarModalQuestion, mountOptions); + return { wrapper }; +} diff --git a/src/layouts/add-to-calendar/calendar-modal-question/calendar-modal-question.vue b/src/layouts/add-to-calendar/calendar-modal-question/calendar-modal-question.vue new file mode 100644 index 000000000..abe4ef39b --- /dev/null +++ b/src/layouts/add-to-calendar/calendar-modal-question/calendar-modal-question.vue @@ -0,0 +1,178 @@ + + + diff --git a/src/layouts/confirmation/confirmation.vue b/src/layouts/confirmation/confirmation.vue index 80532c6a7..05754d6a6 100644 --- a/src/layouts/confirmation/confirmation.vue +++ b/src/layouts/confirmation/confirmation.vue @@ -20,21 +20,7 @@

{{ this.getScheduleDate() }}

{{ this.getScheduleTime() }}

- -
-
- -
-
+
@@ -57,6 +43,7 @@ import funnelHeader from "@/fmg-components/funnel-header/funnel-header"; import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner"; import navbar from "@/fmg-components/nav-bar/nav-bar"; +import addToCalendar from "@/layouts/add-to-calendar/add-to-calendar"; //Supporting files import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { AppointmentTypeStrings } from "@/constants/schedule-constants"; @@ -192,7 +179,7 @@ export default { // Schedule const schedule = store.getters.order.schedule; - const scheduleReqs = !!(schedule.date && schedule.startTime); //removing schedule.endTime for testing + const scheduleReqs = !!(schedule.date && schedule.startTime && schedule.endTime); return serviceLocationReqs && scheduleReqs; }, @@ -245,6 +232,7 @@ export default { funnelHeader, navbar, vehicleBanner, + addToCalendar, }, }; diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue index 2b34185f4..9bce01671 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -191,9 +191,9 @@ export default { this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route); }, async forwardButtonAction() { - this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, null, false); - this.dispatchStoreAction(this.storeActions.SAVE_WORK_ORDER_FLAG, true, false); - await submitWorkOrder({ pageNameToLog: "payment-method" }); + // this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, null, false); + // this.dispatchStoreAction(this.storeActions.SAVE_WORK_ORDER_FLAG, true, false); + // await submitWorkOrder({ pageNameToLog: "payment-method" }); this.$router.navigateWithoutSaving( this.navigationScenarios.CLICKED_FORWARD, this.$route