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 @@
-
+
- {{ amountDueLabel }}
- {{ getDisplayed(amountDue) }}
+ {{ amountDueLabel }}
+ {{ getDisplayed(amountDue) }}
+ id="cart-line-items"
+ class="color-darker-gray">
-
{{ deductibleLabel }}
-
{{ getDisplayed(deductible) }}
+ id="cart-deductible-or-base-price"
+ class="cart-item cart-gray">
+
+ {{ deductibleLabel }}
+ {{ getDisplayed(deductible) }}
+
+
+ {{ basePriceLabel }}
+ {{ getDisplayed(baseServicePrice) }}
+
+
+
+
+
+
+
+
+
+
+ {{ mobileFeeCartItem.name }}
+ {{ getDisplayed(mobileFeeCartItem.subTotal) }}
+
+
+
+
+
+
+
+
+ {{ getDisplayed(recycleFeeCartItem.subTotal) }}
+
+
-
{{ basePriceLabel }}
-
{{ getDisplayed(baseServicePrice) }}
+ id="cart-footer"
+ class="cart-footer">
+
+
+ {{ subtotalLabel }}
+ {{ getDisplayed(subTotal) }}
+
+
+ {{ salesTaxLabel }}
+ {{ formatAmountInDollars(salesTax) }}
+
+
+
+ {{ amountDueLabel }}
+ {{ getDisplayed(amountDue) }}
+
Remove
+
+
+
+
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 @@
+
+
+
+
+
+ {{ value }}
+
+
+
+
+
+
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 @@
+
+
+
+ cmsWidgetName="ShopQuestionWidget"
+ @updatedMobileProviderNumber="setMobileProviderNumber" />
@@ -183,7 +184,12 @@ export default {
const resultMap = await settleAllPromises(promiseResultMap);
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
- vm.setData(resultMap.zipCodeData, resultMap.serviceabilityDetails, resultMap.mobileFeePart);
+ vm.setData(
+ resultMap.zipCodeData,
+ resultMap.serviceabilityDetails,
+ resultMap.mobileFeePart,
+ resultMap.shopQuestionInitialData?.mobileProviderNumber
+ );
vm.$refs.shopQuestion.initializeComponent(resultMap.shopQuestionInitialData);
});
},
@@ -206,6 +212,7 @@ export default {
selectedAppointmentType: this.getSelectedAppointmentType(),
selectedProvider: this.getSelectedProvider(),
mobileFeePart: null,
+ mobileProviderNumber: null,
zipContainsMilitaryBase: false,
zipCodeCtu: null
};
@@ -333,7 +340,23 @@ export default {
await this.$refs.shopQuestion.reloadShopData(zipCode);
},
async forwardButtonAction() {
- useMainStore().saveServiceLocation({
+ let provider = this.selectedProvider;
+ this.mainStore.updateMobileFee(null);
+ if (this.isMobileLocationDisplayed) {
+ this.mainStore.updateMobileFee(this.mobileFeePart);
+ provider = {
+ providerNumber: this.mobileProviderNumber,
+ address: {
+ streetAddress: null,
+ city: null,
+ state: null,
+ zipCode: null,
+ zipCodeCtu: null
+ }
+ };
+ }
+
+ this.mainStore.saveServiceLocation({
address: this.streetAddress,
address2: this.streetAddress2,
city: this.city,
@@ -342,16 +365,7 @@ export default {
zipCodeCtu: this.zipCodeCtu,
appointmentType: this.selectedAppointmentType,
isVehicleProtected: this.isVehicleProtected,
- provider: {
- providerNumber: this.selectedProvider?.providerNumber,
- address: {
- streetAddress: this.selectedProvider?.address?.streetAddress,
- city: this.selectedProvider?.address?.city,
- state: this.selectedProvider?.address?.state,
- zipCode: this.selectedProvider?.address?.zipCode,
- zipCodeCtu: this.selectedProvider?.address?.zipCodeCtu
- }
- }
+ provider
});
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route);
@@ -386,7 +400,7 @@ export default {
getSelectedProvider() {
return useMainStore().order.serviceLocation.provider;
},
- setData(zipCodeData, serviceabilityDetails, mobileFeePart) {
+ setData(zipCodeData, serviceabilityDetails, mobileFeePart, mobileProviderNumber) {
if (zipCodeData) {
this.zipContainsMilitaryBase = zipCodeData.containsMilitaryBase;
this.zipCodeCtu = zipCodeData.zipCodeCtu;
@@ -399,6 +413,10 @@ export default {
if (mobileFeePart) {
this.mobileFeePart = mobileFeePart;
}
+
+ if (mobileProviderNumber) {
+ this.setMobileProviderNumber(mobileProviderNumber);
+ }
},
setContainsMilitaryBase(val) {
if (this.zipContainsMilitaryBase !== val) {
@@ -408,6 +426,9 @@ export default {
setMobileFeePart(mobileFeePart) {
this.mobileFeePart = mobileFeePart;
},
+ setMobileProviderNumber(providerNumber) {
+ this.mobileProviderNumber = providerNumber;
+ },
resetMobileLocation() {
this.streetAddress = '';
this.streetAddress2 = '';
diff --git a/src/layouts/service-location/shop-question/shop-question.vue b/src/layouts/service-location/shop-question/shop-question.vue
index d5fc9da5..47a71198 100644
--- a/src/layouts/service-location/shop-question/shop-question.vue
+++ b/src/layouts/service-location/shop-question/shop-question.vue
@@ -78,7 +78,7 @@ export default {
validationRules: String,
isDisplayed: Boolean
},
- emits: ['update:modelValue'],
+ emits: ['update:modelValue', 'updatedMobileProviderNumber'],
data() {
return {
shopProviders: [],
@@ -237,6 +237,12 @@ export default {
const result = await this.loadData(serviceZipCode);
this.initializeComponent(result.data);
+ if (result.data?.mobileProviderNumber
+ && (this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
+ || this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP)) {
+ this.$emit('updatedMobileProviderNumber', result.data.mobileProviderNumber);
+ }
+
this.resetAnswers();
await nextTick();
diff --git a/src/layouts/tpa-submit/contact-details-drawer/contact-details-drawer.vue b/src/layouts/tpa-submit/contact-details-drawer/contact-details-drawer.vue
index fd5ab900..5f1b0b6f 100644
--- a/src/layouts/tpa-submit/contact-details-drawer/contact-details-drawer.vue
+++ b/src/layouts/tpa-submit/contact-details-drawer/contact-details-drawer.vue
@@ -38,7 +38,7 @@
inputId="phoneNumber"
:cmsWidgetName="widget.phoneNumberQuestion"
isRequired
- :mask="mask"
+ :mask="phoneMask"
disableAutoFill
:validationRules="rules.phoneNumber" />
@@ -53,6 +53,7 @@ import textboxQuestion from '@/digital-components/textbox-question/textbox-quest
// Supporting Files
import { useMainStore } from '@/store/index.js';
import globalRules from '@/constants/global-rules.js';
+import MaskaFormattedMasks from '@/constants/maska-masks';
import widgetFields from '@/constants/cms-widget-fields.js';
export default {
@@ -102,15 +103,8 @@ export default {
footerButtonText() {
return this.getCmsContent(this.widget.drawerFooter, widgetFields.FOOTER_WIDGET.FORWARD_BUTTON_TEXT);
},
- mask() {
- return {
- mask: 'x##-###-####',
- tokens: {
- x: {
- pattern: /[2-9]/
- }
- }
- };
+ phoneMask() {
+ return MaskaFormattedMasks.PHONE_NUMBER;
}
},
methods: {
diff --git a/src/layouts/welcome-page/welcome-page.vue b/src/layouts/welcome-page/welcome-page.vue
index df58619e..30b388f9 100644
--- a/src/layouts/welcome-page/welcome-page.vue
+++ b/src/layouts/welcome-page/welcome-page.vue
@@ -86,7 +86,7 @@
cmsWidgetName="PhoneNumberQuestion"
:validationRules="rules.phoneNumber"
isRequired
- :mask="mask"
+ :mask="phoneMask"
disableAutoFill />
@@ -197,6 +197,7 @@ import { useMainStore } from '@/store';
import states from '@/constants/states';
import globalRules from '@/constants/global-rules';
import routerParams from '@/router/router-constants/router-params';
+import MaskaFormattedMasks from '@/constants/maska-masks';
// define validation rules
defineRule('damage-option-required', required(errorMessages.DAMAGE_OPTION_REQUIRED));
@@ -313,15 +314,8 @@ export default {
maxCoverageLookupAttemptsReached() {
return this.mainStore.applicationUser.coverageLookupAttempts >= 11;
},
- mask() {
- return {
- mask: 'x##-###-####',
- tokens: {
- x: {
- pattern: /[2-9]/
- }
- }
- };
+ phoneMask() {
+ return MaskaFormattedMasks.PHONE_NUMBER;
}
},
methods: {
diff --git a/src/main.js b/src/main.js
index 68987d2e..fa8cc769 100644
--- a/src/main.js
+++ b/src/main.js
@@ -4,7 +4,7 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
import { useMainStore } from '@/store';
import baseMixin from '@/mixins/base-mixin';
import { createPinia } from 'pinia';
-import Maska from 'maska';
+import { vMaska } from 'maska';
import LoadScript from 'vue-plugin-load-script';
import analyticsMixin from '@/mixins/analytics-mixin';
@@ -29,8 +29,8 @@ useMainStore().populateInitialState();
// Additional Vue items to setup
vueApp.use(router);
-vueApp.use(Maska);
vueApp.use(LoadScript);
+vueApp.directive('maska', vMaska);
vueApp.mixin(baseMixin);
vueApp.mixin(analyticsMixin);
vueApp.mixin(experimentMixin);
diff --git a/src/mixins/base-mixin.js b/src/mixins/base-mixin.js
index cac6c7b9..5deec630 100644
--- a/src/mixins/base-mixin.js
+++ b/src/mixins/base-mixin.js
@@ -78,6 +78,10 @@ export default {
});
},
getAmountDue(lineItems) {
+ if (!useMainStore().isNoComp && !useMainStore().policy.isITAC) {
+ return useMainStore().order.currentDeductible;
+ }
+
let amountDue = 0;
if (lineItems.glassParts) {
amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(
diff --git a/src/router/index.js b/src/router/index.js
index 8b1e4823..e9975911 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -136,7 +136,7 @@ router.beforeEach(async (to, from) => {
const toQueryPage = to.query?.issPage;
const notToPayInAdvanceReturn = toQueryPage !== issPageValues.PAYMENT_RETURN;
- const isInIframe = fromQueryPage === issPageValues.PAYMENT_PAGE;
+ const isInIframe = window !== window.top || fromQueryPage === issPageValues.PAYMENT_PAGE;
// isFromPaymentPageToOrderConfirmation workaround for navigating from an iframe but
// isInIframe evaluates to false for some reason when navigating from payment to confirmation
diff --git a/src/store/index.js b/src/store/index.js
index 7f0a595b..5b9e2bd9 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -147,7 +147,8 @@ const getDefaultState = () => ({
glassParts: null,
otherParts: null,
supportingItems: null,
- vaps: null
+ vaps: null,
+ mobileFee: null
},
payment: {
insuranceCoverage: {
@@ -1145,7 +1146,7 @@ export const useMainStore = defineStore({
this.applicationUser.crmCustomerId = response.crmCustomerId.toString();
},
- saveSession() {
+ saveSession({ submitAfterSave }) {
const { vehicle, damage, policy, customer, contactInfo, payment,
lineItems, serviceLocation, schedule } = this.order;
@@ -1233,7 +1234,12 @@ export const useMainStore = defineStore({
coverageStatus: payment.insuranceCoverage?.coverageStatus,
claimNumber: payment.insuranceCoverage?.claimNumber
},
- parentAccountNumber: this.issConfig.parentAccountNumber
+ parentAccountNumber: this.issConfig.parentAccountNumber,
+ paypalToken: payment.paypalToken,
+ isPaypal: payment.payInAdvanceType === paymentMethods.PAYPAL,
+ isCreditCard: payment.payInAdvanceType === paymentMethods.CREDIT_CARD,
+ isAfterpay: payment.payInAdvanceType === paymentMethods.AFTERPAY,
+ CCToken: payment.creditCardToken
},
serviceLocation: {
address: {
@@ -1274,7 +1280,8 @@ export const useMainStore = defineStore({
referralSequenceNumber: this.order.referralSequenceNumber,
eon: this.order.eon,
submitToMainframe: !!this.order.referralNumber,
- loadedFromDupeCheck
+ loadedFromDupeCheck,
+ submitAfterSave: !!submitAfterSave
},
additionalSuccessEventDataHandler: () =>
`Email provided: ${customer.emailAddress ? 'true' : 'false'}`
@@ -1531,6 +1538,10 @@ export const useMainStore = defineStore({
this.order.lineItems.vaps = partsData;
},
+ updateMobileFee(mobileFee) {
+ this.order.lineItems.mobileFee = mobileFee;
+ },
+
updateVehicle(vehicle) {
// Assuming that the method caller pass all the properties.
// otherwise need to check for undefined for every property.
@@ -1869,7 +1880,8 @@ export const useMainStore = defineStore({
);
let queryString = '';
- if (appointmentType === 'Mobile') {
+ if (appointmentType === AppointmentTypeStrings.MOBILE
+ || appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
queryString =
`ParentAccountNumber=${applicationConfig.CASH_PARENT_ACCOUNT_NUMBER}`
+ `&BillToAccountNumber=${billToAccountNumber}`
@@ -2242,6 +2254,7 @@ export const useMainStore = defineStore({
resetPartsAndDependencies() {
this.resetGlassPartsState();
this.updateVaps(null);
+ this.updateMobileFee(null);
this.resetServiceLocationAndDependencies();
},
diff --git a/src/store/store.spec.js b/src/store/store.spec.js
index 3a44c37e..63e55894 100644
--- a/src/store/store.spec.js
+++ b/src/store/store.spec.js
@@ -503,7 +503,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
- await store.saveSession();
+ await store.saveSession({});
// Asserts
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@@ -517,7 +517,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(response));
// Act
- const result = store.saveSession();
+ const result = store.saveSession({});
// Asserts
await expect(result).resolves.toBe(response);
@@ -537,7 +537,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
- await store.saveSession();
+ await store.saveSession({});
// Asserts
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@@ -570,7 +570,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
- await store.saveSession();
+ await store.saveSession({});
// Asserts
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@@ -612,7 +612,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
- await store.saveSession();
+ await store.saveSession({});
// Assert
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@@ -659,7 +659,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
- await store.saveSession();
+ await store.saveSession({});
// Asserts
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@@ -708,7 +708,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
- await store.saveSession();
+ await store.saveSession({});
// Assert
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@@ -742,7 +742,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
- await store.saveSession();
+ await store.saveSession({});
// Assert
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@@ -769,7 +769,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
- await store.saveSession();
+ await store.saveSession({});
// Assert
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@@ -802,7 +802,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
- await store.saveSession();
+ await store.saveSession({});
// Assert
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@@ -838,7 +838,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
- await store.saveSession();
+ await store.saveSession({});
// Assert
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@@ -861,7 +861,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
- await store.saveSession();
+ await store.saveSession({});
// Assert
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@@ -875,7 +875,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
- await store.saveSession();
+ await store.saveSession({});
// Assert
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@@ -888,7 +888,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
- await store.saveSession();
+ await store.saveSession({});
// Asserts
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@@ -904,7 +904,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
- await store.saveSession();
+ await store.saveSession({});
// Asserts
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@@ -927,7 +927,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
- await store.saveSession();
+ await store.saveSession({});
// Asserts
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@@ -941,13 +941,27 @@ describe('Store', () => {
})
}));
});
+ it('calls api with expected submitAfterSave', async () => {
+ // Arrange
+ globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
+
+ // Act
+ await store.saveSession({ submitAfterSave: true });
+
+ // Asserts
+ expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
+ payload: expect.objectContaining({
+ submitAfterSave: true
+ })
+ }));
+ });
it('api call throws exception', async () => {
expect.assertions(2);
const error = 'save session error';
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.reject(error));
// Act
- await store.saveSession().catch((e) => {
+ await store.saveSession({}).catch((e) => {
expect(e).toEqual(error);
});
@@ -1887,4 +1901,17 @@ describe('Store', () => {
expect(store.order.payment.payInAdvanceType).toEqual(paymentMethod);
});
});
+
+ describe('updatePaypalToken method', () => {
+ it('paypalToken is valid when set', () => {
+ // Arrange
+ const paypalToken = getRandomString(6, 6);
+
+ // Act
+ store.updatePaypalToken(paypalToken);
+
+ // Assert
+ expect(store.order.payment.paypalToken).toEqual(paypalToken);
+ });
+ });
});
diff --git a/src/styles/common-styles.scss b/src/styles/common-styles.scss
index afbb0049..357c14d1 100644
--- a/src/styles/common-styles.scss
+++ b/src/styles/common-styles.scss
@@ -64,7 +64,6 @@ body {
left: 50%;
transform: translateX(-50%);
max-width: 576px;
- height: calc(100% - 72px);
&.show {
opacity: 0.4;
@@ -79,6 +78,10 @@ body {
// Prevent scroll when modal is open
&.modal-open {
+ div.page-container-grouped-styles {
+ overflow: hidden;
+ }
+
.fade-on-route-transition {
overflow: hidden;
}
diff --git a/src/styles/ux-variables.scss b/src/styles/ux-variables.scss
index 218bf477..fc4b50ee 100644
--- a/src/styles/ux-variables.scss
+++ b/src/styles/ux-variables.scss
@@ -28,6 +28,7 @@ $red-900: #330300;
// Greens
$green-100: #e3f2ea;
+$green-150: #e6f2ec;
$green-200: #bcedd4;
$green-300: #94d7b6;
$green-400: #5abc8c; // Used in theme