diff --git a/package-lock.json b/package-lock.json index 26792b8c..0ab28ced 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,10 @@ "axios-retry": "^3.5.0", "bootstrap": "^5.3", "iframe-resizer": "^4.3.9", - "maska": "^1.5.0", + "maska": "^2.1.1", "pinia": "^2.1.4", "pinia-plugin-persistedstate": "^2.2.0", + "uuid": "^9.0.1", "vee-validate": "^4.5.7", "vue": "^3.3.4", "vue-plugin-load-script": "^2.1.0", @@ -11599,6 +11600,15 @@ "node": ">=10" } }, + "node_modules/jest-junit/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/jest-leak-detector": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", @@ -13937,9 +13947,9 @@ } }, "node_modules/maska": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/maska/-/maska-1.5.0.tgz", - "integrity": "sha512-BwZXzs5gHeu6wtn3iWFqrKRtcsM3sTpkHvfAngVNVNlN7tl9ZyQUeHTz11s9Sy7Bq1MoQ+xyR/+IzghY8nR84Q==" + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/maska/-/maska-2.1.11.tgz", + "integrity": "sha512-IGqWjBnKxMYcVa06pb4mPfag9sJjnR2T15CdGfQ2llR3gajiSd4AxXCvNqHMEq9W3UBhjjTazgWumsP3sWrUSg==" }, "node_modules/mdn-data": { "version": "2.0.14", @@ -16662,6 +16672,15 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -17730,10 +17749,13 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } diff --git a/package.json b/package.json index 9c287dc0..82ca9450 100644 --- a/package.json +++ b/package.json @@ -19,9 +19,10 @@ "axios-retry": "^3.5.0", "bootstrap": "^5.3", "iframe-resizer": "^4.3.9", - "maska": "^1.5.0", + "maska": "^2.1.1", "pinia": "^2.1.4", "pinia-plugin-persistedstate": "^2.2.0", + "uuid": "^9.0.1", "vee-validate": "^4.5.7", "vue": "^3.3.4", "vue-plugin-load-script": "^2.1.0", @@ -60,4 +61,4 @@ "vitest": "^0.33.0", "volar-service-vetur": "latest" } -} +} \ No newline at end of file diff --git a/src/assets/img/icons/Google.svg b/src/assets/img/icons/Google.svg new file mode 100644 index 00000000..606351ec --- /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 00000000..467b585a --- /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 00000000..f974fb13 --- /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 00000000..0206eccb --- /dev/null +++ b/src/assets/img/icons/Yahoo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/assets/img/icons/add-to-calendar.svg b/src/assets/img/icons/add-to-calendar.svg new file mode 100644 index 00000000..fd621c5c --- /dev/null +++ b/src/assets/img/icons/add-to-calendar.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/img/icons/iCal.svg b/src/assets/img/icons/iCal.svg new file mode 100644 index 00000000..399bcdea --- /dev/null +++ b/src/assets/img/icons/iCal.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/constants/application-config.js b/src/constants/application-config.js index ce39577b..7230f485 100644 --- a/src/constants/application-config.js +++ b/src/constants/application-config.js @@ -19,7 +19,11 @@ const applicationConfig = Object.freeze({ ISS_DEV_CMS_DOMAIN: 'https://digitalisscms.dev.safelite.io', CASH_PARENT_ACCOUNT_NUMBER: 167132, ITAC_FAIR_AND_REASONABLE_BILLTO: '214616', // Lib Mutal FnR BillTo - ITAC_CASH_BILLTO: '283393' // Lib Mutal ITAC Cash BillTo + ITAC_CASH_BILLTO: '283393', // Lib Mutal ITAC Cash BillTo + GOOGLE_CALENDAR: 'https://www.google.com/calendar/render?action=TEMPLATE', + YAHOO_CALENDAR: 'https://calendar.yahoo.com/?v=60', + OUTLOOK_CALENDAR: + 'https://outlook.office.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent' }); export default applicationConfig; diff --git a/src/constants/calendar-options.js b/src/constants/calendar-options.js new file mode 100644 index 00000000..22e9d5ef --- /dev/null +++ b/src/constants/calendar-options.js @@ -0,0 +1,8 @@ +const calendarOptions = Object.freeze({ + ICAL: 'iCal', + GOOGLE: 'Google', + OUTLOOK: 'Outlook', + OUTLOOKCOM: 'Outlook.com', + YAHOO: 'Yahoo' +}); +export default calendarOptions; diff --git a/src/constants/calendar-status.js b/src/constants/calendar-status.js new file mode 100644 index 00000000..5174f33a --- /dev/null +++ b/src/constants/calendar-status.js @@ -0,0 +1,7 @@ +const calendarStatus = Object.freeze({ + FREE: 'Free', + BUSY: 'Busy', + TENTATIVE: 'Tentative', + OUT_OF_THE_OFFICE: 'OutOfTheOffice' +}); +export default calendarStatus; diff --git a/src/constants/maska-masks.js b/src/constants/maska-masks.js new file mode 100644 index 00000000..b7d321c0 --- /dev/null +++ b/src/constants/maska-masks.js @@ -0,0 +1,15 @@ +const MaskaFormattedMasks = { + PHONE_NUMBER: { + mask: 'x##-###-####', + tokens: { + token: 'x', + pattern: '[2-9]' + } + }, + ZIPCODE: { + mask: '#####', + tokens: null + } +}; + +export default MaskaFormattedMasks; diff --git a/src/constants/query-strings.js b/src/constants/query-strings.js index 576d1e5b..81d5ba7d 100644 --- a/src/constants/query-strings.js +++ b/src/constants/query-strings.js @@ -9,6 +9,7 @@ const queryStrings = Object.freeze({ CARD_TYPE: 'sgcardtype', DISPLAY_PAY_IN_ADVANCE_ALERT: 'displayPayInAdvanceAlert', ERROR: 'error', + TOKEN: 'token', LAST_FOUR: 'last_four', REFERENCE_NUMBER: 'req_reference_number', REFERRAL_SEQ_NUM: 'referralseqnum', diff --git a/src/constants/service-type.js b/src/constants/service-type.js new file mode 100644 index 00000000..ebbec62d --- /dev/null +++ b/src/constants/service-type.js @@ -0,0 +1,6 @@ +const serviceType = Object.freeze({ + REPLACEMENT: 'replacement', + REPAIR: 'repair', + REPLACEMENT_AND_RECALIBRATION: 'replacement and recalibration' +}); +export default serviceType; diff --git a/src/digital-components/textbox-question/textbox-question.vue b/src/digital-components/textbox-question/textbox-question.vue index 2ba8d5b9..c97a6b4a 100644 --- a/src/digital-components/textbox-question/textbox-question.vue +++ b/src/digital-components/textbox-question/textbox-question.vue @@ -19,7 +19,9 @@ :id="inputId" :ref="inputId" v-model.trim="value" - v-maska="mask" + v-maska + :data-maska="getMaskFromProp" + :data-maska-tokens="getTokensFromProp" class="form-control" :type="type" :name="inputId" @@ -97,7 +99,7 @@ export default { iconRight: Boolean, // Place icon on right side of text input, otherwise default is left if hasIcon prop is used hasError: Boolean, mask: { - type: String, + type: [String, Object], default: '' }, validationRules: String, @@ -183,6 +185,21 @@ export default { return questionText; } + }, + getMaskFromProp() { + if (this.mask) { + if (this.mask.mask) { + return this.mask.mask; + } + return this.mask; + } + return null; + }, + getTokensFromProp() { + if (this.mask?.tokens) { + return `{ '${this.mask.tokens.token}': { 'pattern': '${this.mask.tokens.pattern}' }}`; + } + return null; } }, watch: { diff --git a/src/helpers/add-to-calendar-helper.js b/src/helpers/add-to-calendar-helper.js new file mode 100644 index 00000000..b00f891b --- /dev/null +++ b/src/helpers/add-to-calendar-helper.js @@ -0,0 +1,75 @@ +import calendarStatus from '@/constants/calendar-status'; +import { getDateFormat } from '@/helpers/date-helper'; + +function ToCalendarFileString(str, replacementArg = '

') { + return str.replace('\r\n', replacementArg); +} + +export function getCalendarFile(calFile) { + const dateFormat = 'yyyyMMddTHHmmss'; + const calEvent = []; + calEvent.push('BEGIN:VCALENDAR'); + calEvent.push('VERSION:2.0'); + calEvent.push('BEGIN:VEVENT'); + calEvent.push(`DTSTAMP:${calFile.TimeStamp}`); + 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'); +} + +export function download(filename, fileBody) { + const 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 be8161e8..70f598a6 100644 --- a/src/helpers/date-helper.js +++ b/src/helpers/date-helper.js @@ -104,3 +104,72 @@ export function get12HourTimeFormat(time) { } return timeString.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); + // eslint-disable-next-line consistent-return + 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 padTo2Digits(time) { + // Use the built-in method toString() with a radix of 10 to convert the time value to a decimal string + // eslint-disable-next-line no-param-reassign + time = time.toString(10); + // Use the conditional operator to check if the length of the string is less than 2 + return time.length < 2 + // If yes, prepend a '0' to the string and return it + // If no, return the original string + ? `0${time}` : time; +} + +export function convertMsToTime(milliseconds) { + let seconds = Math.floor(milliseconds / 1000); + let minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + + seconds %= 60; + minutes %= 60; + // commenting to get 24 time format + // hours = hours % 24; + + return `${padTo2Digits(hours)}${padTo2Digits(minutes)}`; +} + +export function calculateDuration(startDate, endDate) { + if (startDate instanceof Date !== true) return; + if (endDate instanceof Date !== true) return; + // eslint-disable-next-line consistent-return + return convertMsToTime(endDate - startDate); +} + +export function combineDateAndTime(date, time) { + // Use the Date.parse() method to convert the date and time strings to a numeric value + const timestamp = Date.parse(`${date}T${time}`); + // Use the new Date() constructor to create a new date object from the numeric value + const newDate = new Date(timestamp); + // Return the new date object + return newDate; +} +export function addMinutes(date, minutes) { + return new Date(date.getTime() + minutes * 60000); +} +export function shortTimeString(date) { + // Use a ternary operator to check if the input is a valid date object + return date instanceof Date + // Use the built-in method toLocaleTimeString() to get the short time string in the current locale + // Return undefined if the input is not a valid date object + ? date.toLocaleTimeString('en-us', { hour: 'numeric', minute: 'numeric', hour12: true }) : undefined; +} diff --git a/src/helpers/order-helper.js b/src/helpers/order-helper.js index 33e7d3c4..97b685cd 100644 --- a/src/helpers/order-helper.js +++ b/src/helpers/order-helper.js @@ -4,8 +4,8 @@ import { updateOrCreateISSCookie } from '@/helpers/cookie-helper'; /* Encapsulates asynchronous Save Session logic inside a promise to allow for Save Session queuing */ -async function saveSessionHelper(store) { - const savedSessionInfo = await store.saveSession(); +async function saveSessionHelper(store, { submitAfterSave }) { + const savedSessionInfo = await store.saveSession({ submitAfterSave }); if (savedSessionInfo) { store.setSaveSessionInfo(savedSessionInfo.data); } @@ -17,11 +17,11 @@ async function saveSessionHelper(store) { This will also set Referral information in the store after saving, and then update the cookie. To force synchronous behavior pass in 'true' for shouldAwaitSaveSessionQueue */ -export async function saveSession({ shouldAwaitSaveSessionQueue = false }) { +export async function saveSession({ shouldAwaitSaveSessionQueue = false, submitAfterSave = false }) { const store = useMainStore(); const saveSessionPromise = store.applicationUser.saveSessionPromise - ? store.applicationUser.saveSessionPromise.then(() => saveSessionHelper(store)) - : saveSessionHelper(store); + ? store.applicationUser.saveSessionPromise.then(() => saveSessionHelper(store, { submitAfterSave })) + : saveSessionHelper(store, { submitAfterSave }); store.setSaveSessionPromise(saveSessionPromise); diff --git a/src/iss-components/cart-dropdown/__snapshots__/cart-dropdown.spec.js.snap b/src/iss-components/cart-dropdown/__snapshots__/cart-dropdown.spec.js.snap index df4a8ae4..e6e66827 100644 --- a/src/iss-components/cart-dropdown/__snapshots__/cart-dropdown.spec.js.snap +++ b/src/iss-components/cart-dropdown/__snapshots__/cart-dropdown.spec.js.snap @@ -5,5 +5,14 @@ Object { "baseServiceLineItems": Array [], "deductible": null, "isExpanded": false, + "widget": Object { + "amountDue": "AmountDueTextWidget", + "basePrice": "BasePriceWidget", + "deductible": "DeductibleWidget", + "mobileFee": "MobileServiceWidget", + "recycleFee": "RecycleFeeWidget", + "salesTax": "SalesTaxWidget", + "subtotal": "SubtotalWidget", + }, } `; diff --git a/src/iss-components/cart-dropdown/cart-dropdown.spec.js b/src/iss-components/cart-dropdown/cart-dropdown.spec.js index 16a35951..fc09a48e 100644 --- a/src/iss-components/cart-dropdown/cart-dropdown.spec.js +++ b/src/iss-components/cart-dropdown/cart-dropdown.spec.js @@ -7,6 +7,7 @@ import { getMountOptions } from '@/helpers/unit-test-helper.js'; import { useMainStore } from '@/store'; import coverageStatuses from '@/constants/coverage-statuses'; import { formatAmountInDollars } from '@/helpers/text-helper.js'; +import partTypeStrings from '@/constants/part-type-strings'; const VERIFYING_COVERAGE = 'Verifying coverage'; @@ -41,7 +42,13 @@ function getMountedComponent(mainInitialState = {}, initialData = {}, propsData describe('cart-dropdown component', () => { test('initial data rendered as expected', () => { // Arrange - const { wrapper } = getMountedComponent(); + const { wrapper } = getMountedComponent({ + order: { + lineItems: { + supportingItems: [] + } + } + }); // Assert expect(wrapper.vm.$data).toMatchSnapshot(); @@ -121,6 +128,58 @@ describe('cart-dropdown component', () => { // Assert expect(cartBasePrice.exists()).toBeTruthy(); }); + test('cart footer', () => { + // Arrange + const reference = '#cart-footer'; + const isExpanded = true; + const initialData = { isExpanded }; + const { wrapper } = getMountedComponent({}, {}, initialData); + + // Act + const cartFooter = wrapper.find(reference); + + // Assert + expect(cartFooter.exists()).toBeTruthy(); + }); + test('subtotal', () => { + // Arrange + const reference = '#cart-subtotal'; + const isExpanded = true; + const initialData = { isExpanded }; + const { wrapper } = getMountedComponent({}, {}, initialData); + + // Act + const subtotal = wrapper.find(reference); + + // Assert + expect(subtotal.exists()).toBeTruthy(); + }); + test('tax', () => { + // Arrange + const reference = '#cart-sales-tax'; + const isExpanded = true; + const initialData = { isExpanded }; + const { wrapper } = getMountedComponent({}, {}, initialData); + + // Act + const salesTax = wrapper.find(reference); + + // Assert + expect(salesTax.exists()).toBeTruthy(); + }); + test('bottom amount due', () => { + // Arrange + const reference = '#bottom-amount-due'; + const isExpanded = true; + const initialData = { isExpanded }; + const { wrapper } = getMountedComponent({}, {}, initialData); + + // Act + const bottomAmountDue = wrapper.find(reference); + + // Assert + expect(bottomAmountDue.exists()).toBeTruthy(); + }); }); describe('does not display', () => { test('cart deductible when showDeductibleLineItem is false', () => { @@ -516,5 +575,85 @@ describe('cart-dropdown component', () => { expect(result).toBe(dollarAmount); }); }); + describe('Recyle fee', () => { + test('Shows recycle fee when preset in supported items', () => { + // Arrange + const reference = '#recycle-fee-item'; + const {wrapper} = getMountedComponent({ + order: { + lineItems: { + supportingItems: [{ + partType: partTypeStrings.REPLACE_FEE + }] + } + } + }); + + // Act + const cartDeductible = wrapper.find(reference); + + // Assert + expect(wrapper.vm.recycleFeeCartItem).not.toBeNull(); + expect(cartDeductible.exists()).toBeTruthy(); + }); + test('Hides recycle fee when not in supported items', () => { + // Arrange + const reference = '#recycle-fee-item'; + const {wrapper} = getMountedComponent({ + order: { + lineItems: { + supportingItems: [] + } + } + }); + + // Act + const cartDeductible = wrapper.find(reference); + + // Assert + expect(wrapper.vm.recycleFeeCartItem).toBeNull(); + expect(cartDeductible.exists()).toBeFalsy(); + }); + }); + describe('Mobile Fee', () => { + test('Shows mobile fee when preset', () => { + // Arrange + const reference = '#mobile-fee-item'; + const { wrapper } = getMountedComponent({ + order: { + lineItems: { + supportingItems: [], + mobileFee: {} + } + } + }); + + // Act + const cartDeductible = wrapper.find(reference); + + // Assert + expect(wrapper.vm.mobileFeeCartItem).not.toBeNull(); + expect(cartDeductible.exists()).toBeTruthy(); + }); + test('Hides recycle fee when not in supported items', () => { + // Arrange + const reference = '#mobile-fee-item'; + const { wrapper } = getMountedComponent({ + order: { + lineItems: { + supportingItems: [], + mobileFee: null + } + } + }); + + // Act + const cartDeductible = wrapper.find(reference); + + // Assert + expect(wrapper.vm.mobileFeeCartItem).toBeNull(); + expect(cartDeductible.exists()).toBeFalsy(); + }); + }); }); }); diff --git a/src/iss-components/cart-dropdown/cart-dropdown.vue b/src/iss-components/cart-dropdown/cart-dropdown.vue index 674c49c7..43b8f62a 100644 --- a/src/iss-components/cart-dropdown/cart-dropdown.vue +++ b/src/iss-components/cart-dropdown/cart-dropdown.vue @@ -1,5 +1,5 @@ + diff --git a/src/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-list-button/calendar-modal-list-button.vue b/src/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-list-button/calendar-modal-list-button.vue new file mode 100644 index 00000000..814d10fe --- /dev/null +++ b/src/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-list-button/calendar-modal-list-button.vue @@ -0,0 +1,81 @@ + + + diff --git a/src/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-question.spec.js b/src/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-question.spec.js new file mode 100644 index 00000000..51b43508 --- /dev/null +++ b/src/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-question.spec.js @@ -0,0 +1,41 @@ +import { shallowMount } from '@vue/test-utils'; +import calendarModalQuestion from '@/layouts/order-confirmation/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, + name, + icon + } + ] + ]; + + const wrapper = shallowMount(calendarModalQuestion, { + propsData: { + calendarOptionsData: [ + { + url, + name, + icon + } + ] + } + }); + + wrapper.vm.$refs.calendarOptions.closeModal = jest.fn(); + wrapper.vm.selectedCalendarOption = name; + + // Act + wrapper.vm.setSelectedCalendarOption(); + + // Assert + expect(wrapper.emitted()['update:modelValue']).toEqual(expectedEmit); + }); +}); diff --git a/src/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-question.vue b/src/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-question.vue new file mode 100644 index 00000000..702abf1b --- /dev/null +++ b/src/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-question.vue @@ -0,0 +1,180 @@ + + + diff --git a/src/layouts/order-confirmation/order-confirmation.spec.js b/src/layouts/order-confirmation/order-confirmation.spec.js index 99ba8fa7..47e0f137 100644 --- a/src/layouts/order-confirmation/order-confirmation.spec.js +++ b/src/layouts/order-confirmation/order-confirmation.spec.js @@ -39,6 +39,10 @@ const vehicleBannerStub = { render: () => {} }; +const addToCalendarStub = { + render: () => {} +}; + const initialStore = { order: { schedule: { @@ -127,7 +131,8 @@ function getMountedComponent(mainInitialState = {}, initialData = {}, methodToRu mountOptions.global.stubs = { siteFooter: footerStub, siteHeader: headerStub, - vehicleBanner: vehicleBannerStub + vehicleBanner: vehicleBannerStub, + addToCalendar: addToCalendarStub }; const testingPinia = createTestingPinia({ diff --git a/src/layouts/order-confirmation/order-confirmation.vue b/src/layouts/order-confirmation/order-confirmation.vue index fa7a4c14..7ee31d95 100644 --- a/src/layouts/order-confirmation/order-confirmation.vue +++ b/src/layouts/order-confirmation/order-confirmation.vue @@ -25,6 +25,19 @@

{{ appointmentDateFormatted }}

{{ appointmentTimeFormatted }}

+
@@ -52,6 +65,7 @@ import siteHeader from '@/iss-components/site-header/site-header.vue'; import vehicleBanner from '@/iss-components/vehicle-banner/vehicle-banner.vue'; import siteFooter from '@/iss-components/site-footer/site-footer.vue'; +import addToCalendar from '@/layouts/order-confirmation/add-to-calendar/add-to-calendar.vue'; // Supporting files import { fetchCmsContentForPage, processIfStatements } from '@/helpers/cms-content-helper'; import settleAllPromises from '@/helpers/layout-helper'; @@ -72,7 +86,8 @@ export default { Form, siteHeader, vehicleBanner, - siteFooter + siteFooter, + addToCalendar }, mixins: [BaseFormMixin], async beforeRouteEnter(to, from, next) { @@ -180,7 +195,7 @@ export default { }, providerFullAddress() { // eslint-disable-next-line max-len - return `${this.providerAddress},
${this.providerCity}, ${this.providerState} ${this.providerZipCode}`; + return this.submittedOrder.serviceLocation?.provider?.address ? `${this.providerAddress},
${this.providerCity}, ${this.providerState} ${this.providerZipCode}` : ''; }, appointmentWordingText() { switch (this.appointmentType) { @@ -297,15 +312,15 @@ $page-side-padding: 1.5rem; .appointment-details { margin-bottom: 1.5rem; padding: 1rem 1.5rem 1.5rem; - box-shadow: 0 3px 10px rgb(0 0 0 / 0.2); - border-radius: 5px; + box-shadow: 0 0.188rem 0.625rem rgb(0 0 0 / 0.2); + border-radius: 0.313rem; } .appointment-date-time P { color: $black; font-size: $h5-font-size; line-height: map-get($spacers, 6); - margin-bottom: 0.5rem; + margin-bottom: 0; + p { font-size: map-get($spacers, 4); diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue index b71e9155..092c62a0 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -21,11 +21,15 @@
+======= + recyclingModalCmsWidgetName="RecycleModal" /> +>>>>>>> feafc2e3accf2c6901ff1b6739e44c60c44fd8aa
Pia Alert Placeholder
>>>>>> feafc2e3accf2c6901ff1b6739e44c60c44fd8aa } }, watch: { diff --git a/src/layouts/payment-page/payment-page.spec.js b/src/layouts/payment-page/payment-page.spec.js index 4aea7c28..2e2f62ef 100644 --- a/src/layouts/payment-page/payment-page.spec.js +++ b/src/layouts/payment-page/payment-page.spec.js @@ -6,6 +6,7 @@ import { shallowMount } from '@vue/test-utils'; import { getMountOptions } from '@/helpers/unit-test-helper'; import { useMainStore } from '@/store'; import { paymentMethods, hopPaymentMethods } from '@/constants/payment-method-constants.js'; +import queryStrings from '@/constants/query-strings'; // Constants const parts = { @@ -116,10 +117,10 @@ jest.mock('@/helpers/cms-content-helper', () => ({ fetchCmsContentForPage: () => Promise.resolve('content') })); -function setupMocks({ customMountOptions }) { +function setupMocks({ customMountOptions = {}, queryString }) { const mountOptions = getMountOptions({ ...customMountOptions, - route: { query: { issPage: 'page-name' }, params: {} } + route: { query: { issPage: 'page-name', ...queryString }, params: {} } }); // set all mock stuff @@ -450,4 +451,38 @@ describe('payment-page.vue', () => { expect(clickFn).not.toBeCalled(); }); }); + + describe('Credit Card Error Alert', () => { + test('show credit card error alert', () => { + // Arrange + const queryString = { + [queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT]: paymentMethods.CREDIT_CARD + }; + const wrapper = setupMocks({ queryString }); + + // Act + + // Assert + const creditCardErrorAlert = wrapper.find('[name="payInAdvanceCreditCardErrorAlert"]'); + expect(creditCardErrorAlert.exists()).toBeTruthy(); + expect(creditCardErrorAlert.isVisible()).toBeTruthy(); + }); + }); + + describe('AfterPay Error Alert', () => { + test('show after pay error alert', () => { + // Arrange + const queryString = { + [queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT]: paymentMethods.AFTERPAY + }; + const wrapper = setupMocks({ queryString }); + + // Act + + // Assert + const afterPayErrorAlert = wrapper.find('[name="payInAdvanceAfterPayErrorAlert"]'); + expect(afterPayErrorAlert.exists()).toBeTruthy(); + expect(afterPayErrorAlert.isVisible()).toBeTruthy(); + }); + }); }); diff --git a/src/layouts/payment-page/payment-page.vue b/src/layouts/payment-page/payment-page.vue index b230d5ee..3c0c8913 100644 --- a/src/layouts/payment-page/payment-page.vue +++ b/src/layouts/payment-page/payment-page.vue @@ -4,6 +4,23 @@
+ + +