Merge branch 'develop' into feature/CASH-2032
This commit is contained in:
commit
5549b6befd
73 changed files with 2366 additions and 3549 deletions
|
|
@ -13,6 +13,8 @@ const environmentVariables = {
|
||||||
['__VUE_APP_SAFELITE_HOP__']: "https://sv2-safelitehop-dev.safelite.com/fmgCheckoutShared.aspx",
|
['__VUE_APP_SAFELITE_HOP__']: "https://sv2-safelitehop-dev.safelite.com/fmgCheckoutShared.aspx",
|
||||||
['__VUE_APP_SESSION_TIMEOUT_MINUTES__']: 30,
|
['__VUE_APP_SESSION_TIMEOUT_MINUTES__']: 30,
|
||||||
['__VUE_APP_SOLARWINDS_MONITORING_SCRIPT__']: "",
|
['__VUE_APP_SOLARWINDS_MONITORING_SCRIPT__']: "",
|
||||||
|
['__VUE_APP_ADYEN_ENVIRONMENT__']: "test",
|
||||||
|
['__VUE_APP_ADYEN_CLIENT_KEY__']: "test_WYGT4F5ZT5BRRL5MPWHK6QLYOIXZXROD",
|
||||||
};
|
};
|
||||||
|
|
||||||
const keysOnly = Object.keys(environmentVariables);
|
const keysOnly = Object.keys(environmentVariables);
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ module.exports = {
|
||||||
},
|
},
|
||||||
transformIgnorePatterns: ["'/node_modules/(?!vee-validate)"],
|
transformIgnorePatterns: ["'/node_modules/(?!vee-validate)"],
|
||||||
moduleFileExtensions: ["js", "vue"],
|
moduleFileExtensions: ["js", "vue"],
|
||||||
modulePathIgnorePatterns: ["vin-lookup"],
|
|
||||||
collectCoverageFrom: [
|
collectCoverageFrom: [
|
||||||
"src/**/*.{js,vue}",
|
"src/**/*.{js,vue}",
|
||||||
"!src/main.js",
|
"!src/main.js",
|
||||||
|
|
@ -33,11 +32,7 @@ module.exports = {
|
||||||
"!src/layouts/payment-pia-return/*.vue", // Temp test exclusion while in development
|
"!src/layouts/payment-pia-return/*.vue", // Temp test exclusion while in development
|
||||||
"!src/layouts/insurance/*.vue", // Temp test exclusion while in development
|
"!src/layouts/insurance/*.vue", // Temp test exclusion while in development
|
||||||
"!src/layouts/insurance-company/*.vue", // Temp test exclusion while in development
|
"!src/layouts/insurance-company/*.vue", // Temp test exclusion while in development
|
||||||
"!src/layouts/schedule/**/*.vue", // Temp test exclusion while in development
|
|
||||||
"!src/digital-components/date-picker/**/*.vue", // Temp test exclusion while in development
|
"!src/digital-components/date-picker/**/*.vue", // Temp test exclusion while in development
|
||||||
|
|
||||||
"!src/**/*-june-2025.vue", // Exclude these temporary files for CASH-845 project
|
|
||||||
|
|
||||||
"!src/layouts/insurance-company/insurance-company-question/*.vue", // Temp test exclusion while in development
|
"!src/layouts/insurance-company/insurance-company-question/*.vue", // Temp test exclusion while in development
|
||||||
"!src/experiment-components/*.vue",
|
"!src/experiment-components/*.vue",
|
||||||
// END
|
// END
|
||||||
|
|
@ -45,7 +40,7 @@ module.exports = {
|
||||||
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
||||||
coverageThreshold: {
|
coverageThreshold: {
|
||||||
global: {
|
global: {
|
||||||
statements: 64,
|
statements: 68,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit
|
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en-US">
|
<html lang="en-US">
|
||||||
<head>
|
<head>
|
||||||
|
<meta name="robots" content="noindex" />
|
||||||
<script>
|
<script>
|
||||||
window.dataLayer = [{}];
|
window.dataLayer = [{}];
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -545,7 +545,6 @@
|
||||||
{ path: '/parts/parts-or-questions', method: 'POST', body: {"carId":"CR00046193","glassPieces":[{"location":"Windshield","name":"Single"}],"zip":"43015","vin":null,"serviceType":null,"referralSeqNumber":environmentContext.referralNumber,"AppName":"FixMyGlass"} },
|
{ path: '/parts/parts-or-questions', method: 'POST', body: {"carId":"CR00046193","glassPieces":[{"location":"Windshield","name":"Single"}],"zip":"43015","vin":null,"serviceType":null,"referralSeqNumber":environmentContext.referralNumber,"AppName":"FixMyGlass"} },
|
||||||
{ path: '/parts/wipers/CR00046193/43015', method: 'GET' },
|
{ path: '/parts/wipers/CR00046193/43015', method: 'GET' },
|
||||||
{ path: '/parts/mobile-premium-fee/Cash/Replace', method: 'GET' },
|
{ path: '/parts/mobile-premium-fee/Cash/Replace', method: 'GET' },
|
||||||
{ path: '/parts/service-package-discount', method: 'POST', body: {"glassPieces":[{"location":"Windshield","name":"Single"}],"damageType":"Replace","AppName":"FixMyGlass"} },
|
|
||||||
{ path: '/parts/mobile-fee/?serviceType=Install&facilityType=Mobile&parentAccountNumber=167132&billToAccountNumber=87291&providerNumber=03357&isItacOptimized=false&zipCode=43015', method: 'GET' },
|
{ path: '/parts/mobile-fee/?serviceType=Install&facilityType=Mobile&parentAccountNumber=167132&billToAccountNumber=87291&providerNumber=03357&isItacOptimized=false&zipCode=43015', method: 'GET' },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ const GaCategories = {
|
||||||
EVOX: "Evox",
|
EVOX: "Evox",
|
||||||
APPOINTMENT: "Appointment",
|
APPOINTMENT: "Appointment",
|
||||||
SERVICE_LOCATION: "service-location",
|
SERVICE_LOCATION: "service-location",
|
||||||
|
CONFIRMATION_CLICKED: "confirmation clicked",
|
||||||
|
RECAL_DISCLAIMER: "Recalibration disclaimer",
|
||||||
};
|
};
|
||||||
|
|
||||||
const GaActions = {
|
const GaActions = {
|
||||||
|
|
@ -26,6 +28,9 @@ const GaActions = {
|
||||||
MOBILE_AVAILABLE: "mobile_available",
|
MOBILE_AVAILABLE: "mobile_available",
|
||||||
SHOPS_FIRST_DISPLAYED: "shops_first_displayed",
|
SHOPS_FIRST_DISPLAYED: "shops_first_displayed",
|
||||||
MORE_LOCATIONS_CLICKED: "more_locations_clicked",
|
MORE_LOCATIONS_CLICKED: "more_locations_clicked",
|
||||||
|
YES: "yes",
|
||||||
|
NO: "no",
|
||||||
|
ACKNOWLEDGED: "Acknowledged",
|
||||||
};
|
};
|
||||||
|
|
||||||
const GaLabels = {
|
const GaLabels = {
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ const applicationConfig = {
|
||||||
COOKIE_NAME_LENGTH_MAX_LIMIT: 44,
|
COOKIE_NAME_LENGTH_MAX_LIMIT: 44,
|
||||||
SESSION_TIMEOUT_MINUTES: process.env.__VUE_APP_SESSION_TIMEOUT_MINUTES__,
|
SESSION_TIMEOUT_MINUTES: process.env.__VUE_APP_SESSION_TIMEOUT_MINUTES__,
|
||||||
RETURN_USER_PAGE: "/fmg/return-user",
|
RETURN_USER_PAGE: "/fmg/return-user",
|
||||||
|
ADYEN_CLIENT_KEY: process.env.__VUE_APP_ADYEN_CLIENT_KEY__,
|
||||||
|
ADYEN_ENVIRONMENT: process.env.__VUE_APP_ADYEN_ENVIRONMENT__,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { applicationConfig };
|
export { applicationConfig };
|
||||||
|
|
|
||||||
|
|
@ -76,10 +76,6 @@ const endpoints = {
|
||||||
url: "/parts/api/v1/parts/pricing-by-day-part-number",
|
url: "/parts/api/v1/parts/pricing-by-day-part-number",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
},
|
},
|
||||||
GetServicePackageDiscountPart: {
|
|
||||||
url: "/parts/api/v1/parts/service-package-discount",
|
|
||||||
method: "POST",
|
|
||||||
},
|
|
||||||
GetServiceabilityDetails: {
|
GetServiceabilityDetails: {
|
||||||
url: "/location/api/v1/location/serviceability-details",
|
url: "/location/api/v1/location/serviceability-details",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
|
@ -162,7 +158,7 @@ const endpoints = {
|
||||||
},
|
},
|
||||||
TaxOrderItems: {
|
TaxOrderItems: {
|
||||||
url: "/price/api/v1/price/taxed-order-items",
|
url: "/price/api/v1/price/taxed-order-items",
|
||||||
method: "GET",
|
method: "POST",
|
||||||
},
|
},
|
||||||
LogExperimentExposureIfAssigned: {
|
LogExperimentExposureIfAssigned: {
|
||||||
url: "/experiments/api/v1/experiments/log-exposure",
|
url: "/experiments/api/v1/experiments/log-exposure",
|
||||||
|
|
@ -216,6 +212,10 @@ const endpoints = {
|
||||||
url: "/order/api/v1/order/add-donation-part",
|
url: "/order/api/v1/order/add-donation-part",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
},
|
},
|
||||||
|
GetOemAfterMarketParts: {
|
||||||
|
url: "/parts/api/v1/parts/oem-after-market-parts",
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
InitializeAdyenPayment: {
|
InitializeAdyenPayment: {
|
||||||
url: "/payment/api/v1/payment/payment/session/initialize-session",
|
url: "/payment/api/v1/payment/payment/session/initialize-session",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,9 @@ const experimentSettings = {
|
||||||
SUPPRESS_VIN_CAPTURE: "SuppressVinCapture",
|
SUPPRESS_VIN_CAPTURE: "SuppressVinCapture",
|
||||||
DISPLAY_AVAILABILITY_INDICATORS: "DisplayAvailabilityIndicators",
|
DISPLAY_AVAILABILITY_INDICATORS: "DisplayAvailabilityIndicators",
|
||||||
PIA_EXPERIENCE: "PIA Experience",
|
PIA_EXPERIENCE: "PIA Experience",
|
||||||
|
CONDITIONAL_PROVIDER_NUMBERS: "ConditionalProviderNumbers",
|
||||||
|
CONDITIONAL_PROVIDER_NUMBERS_2: "ConditionalProviderNumbers2",
|
||||||
|
CONDITIONAL_PROVIDER_SHUTOFF_DATE: "ConditionalProviderShutoffDate",
|
||||||
PIA_INSURANCE: "DisplayPIAInsurance",
|
PIA_INSURANCE: "DisplayPIAInsurance",
|
||||||
SUBMIT_ORDER_ENABLE_PIA: "SubmitOrder_Enable_PIA",
|
SUBMIT_ORDER_ENABLE_PIA: "SubmitOrder_Enable_PIA",
|
||||||
IS_EMAIL_OPTIONAL: "isEmailOptional",
|
IS_EMAIL_OPTIONAL: "isEmailOptional",
|
||||||
|
|
@ -33,6 +36,10 @@ const experimentSettings = {
|
||||||
SHOW_PM_MOBILE_DAYS: "Show_PMmobileDays",
|
SHOW_PM_MOBILE_DAYS: "Show_PMmobileDays",
|
||||||
SHOW_NO_PM_MOBILE_DAYS: "Show_NoPMmobileDays",
|
SHOW_NO_PM_MOBILE_DAYS: "Show_NoPMmobileDays",
|
||||||
SHOW_NO_MOBILE_AVAILABLE_DAYS: "Show_NoMobileAvailableDays",
|
SHOW_NO_MOBILE_AVAILABLE_DAYS: "Show_NoMobileAvailableDays",
|
||||||
|
SHOW_MOBILE_FIRST_POPUP_REPAIR: "Show_Mobile_First_Popup_Repair",
|
||||||
|
SHOW_MOBILE_FIRST_POPUP_REPLACE_WITHOUT_MSR: "Show_Mobile_First_Popup_Replace_Without_MSR",
|
||||||
|
SHOW_MOBILE_FIRST_POPUP_REPLACE_WITH_MSR: "Show_Mobile_First_Popup_Replace_With_MSR",
|
||||||
|
OFFER_OEM: "OfferOem",
|
||||||
USE_ADYEN_PAYMENT: "UseAdyenPayment",
|
USE_ADYEN_PAYMENT: "UseAdyenPayment",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const partTypeStrings = {
|
||||||
SERVICE_PACKAGE_DISCOUNT: "SERVICE PACKAGE DISCOUNT",
|
SERVICE_PACKAGE_DISCOUNT: "SERVICE PACKAGE DISCOUNT",
|
||||||
QUOTE_PAGE_DISCOUNT: "QUOTE PAGE DISCOUNT",
|
QUOTE_PAGE_DISCOUNT: "QUOTE PAGE DISCOUNT",
|
||||||
DONATION: "DONATION",
|
DONATION: "DONATION",
|
||||||
|
WINDSHIELD: "WINDSHIELD",
|
||||||
};
|
};
|
||||||
|
|
||||||
export { partTypeStrings };
|
export { partTypeStrings };
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,10 @@ export const pageProgressMapper = {
|
||||||
percent: 30,
|
percent: 30,
|
||||||
step: 1,
|
step: 1,
|
||||||
},
|
},
|
||||||
|
"policy-info": {
|
||||||
|
percent: 55,
|
||||||
|
step: 2,
|
||||||
|
},
|
||||||
quote: {
|
quote: {
|
||||||
percent: 60,
|
percent: 60,
|
||||||
step: 2,
|
step: 2,
|
||||||
|
|
@ -80,6 +84,10 @@ export const pageProgressMapper = {
|
||||||
percent: 98.5,
|
percent: 98.5,
|
||||||
step: 4,
|
step: 4,
|
||||||
},
|
},
|
||||||
|
"payment-adyen": {
|
||||||
|
percent: 98.5,
|
||||||
|
step: 4,
|
||||||
|
},
|
||||||
confirmation: {
|
confirmation: {
|
||||||
percent: 100,
|
percent: 100,
|
||||||
step: 4,
|
step: 4,
|
||||||
|
|
|
||||||
|
|
@ -77,4 +77,29 @@ export const quotePageDiscountTable = [
|
||||||
description: "PREM RCL GEOTARGET DISC",
|
description: "PREM RCL GEOTARGET DISC",
|
||||||
packageLevel: packageNames.TIER_THREE,
|
packageLevel: packageNames.TIER_THREE,
|
||||||
},
|
},
|
||||||
|
// Replace - Curve Tempered
|
||||||
|
{
|
||||||
|
name: "Curve Tempered Glass Only",
|
||||||
|
experimentCode: "Show_GlassOnly_Replace_CT_Discount",
|
||||||
|
partNumber: "GL CT CSH PKG",
|
||||||
|
partType: partTypeStrings.QUOTE_PAGE_DISCOUNT,
|
||||||
|
description: "GLASS CT GEOTARGET DISC",
|
||||||
|
packageLevel: packageNames.TIER_ONE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Curve Tempered Standard",
|
||||||
|
experimentCode: "Show_Standard_Replace_CT_Discount",
|
||||||
|
partNumber: "STD CT CSH PKG",
|
||||||
|
partType: partTypeStrings.QUOTE_PAGE_DISCOUNT,
|
||||||
|
description: "STD CT GEOTARGET DISC",
|
||||||
|
packageLevel: packageNames.TIER_TWO,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Curve Tempered Premium",
|
||||||
|
experimentCode: "Show_Premium_Replace_CT_Discount",
|
||||||
|
partNumber: "PRM CT CSH PKG",
|
||||||
|
partType: partTypeStrings.QUOTE_PAGE_DISCOUNT,
|
||||||
|
description: "PREM CT GEOTARGET DISC",
|
||||||
|
packageLevel: packageNames.TIER_THREE,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ const PREMIUM_FEE_PART_TYPE = "EARLY BIRD";
|
||||||
|
|
||||||
const PRICING_BY_DAY_PART_TYPE = "PREMAPPT GEN";
|
const PRICING_BY_DAY_PART_TYPE = "PREMAPPT GEN";
|
||||||
|
|
||||||
|
const RECAL_ACK_YES = "Yes";
|
||||||
|
|
||||||
const RouteCodeFlags = {
|
const RouteCodeFlags = {
|
||||||
ALL_DAY_DROP_OFF: "ALL DAY DROP OFF",
|
ALL_DAY_DROP_OFF: "ALL DAY DROP OFF",
|
||||||
OVERNIGHT_DROP_OFF: "OVERNIGHT DROP OFF",
|
OVERNIGHT_DROP_OFF: "OVERNIGHT DROP OFF",
|
||||||
|
|
@ -31,4 +33,5 @@ export {
|
||||||
PRICING_BY_DAY_PART_TYPE,
|
PRICING_BY_DAY_PART_TYPE,
|
||||||
RouteCodeFlags,
|
RouteCodeFlags,
|
||||||
cmsWidgetFieldMappings,
|
cmsWidgetFieldMappings,
|
||||||
|
RECAL_ACK_YES,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ const storeActions = {
|
||||||
GET_ALERT_REASONS_BY_CTU: "getAlertReasonsByCtu",
|
GET_ALERT_REASONS_BY_CTU: "getAlertReasonsByCtu",
|
||||||
GET_PARTS_OR_QUESTIONS: "getPartsOrQuestions",
|
GET_PARTS_OR_QUESTIONS: "getPartsOrQuestions",
|
||||||
GET_PARTS: "getParts",
|
GET_PARTS: "getParts",
|
||||||
|
GET_OEM_AFTERMARKET_PARTS: "getOemAfterMarketParts",
|
||||||
GET_WIPERS: "getWipers",
|
GET_WIPERS: "getWipers",
|
||||||
GET_RAIN_REPEL: "getRainRepel",
|
GET_RAIN_REPEL: "getRainRepel",
|
||||||
GET_SUPPORTING_ITEMS: "getSupportingItems",
|
GET_SUPPORTING_ITEMS: "getSupportingItems",
|
||||||
|
|
@ -32,7 +33,6 @@ const storeActions = {
|
||||||
GET_MOLDING_QUESTIONS: "getMoldingQuestions",
|
GET_MOLDING_QUESTIONS: "getMoldingQuestions",
|
||||||
GET_MOBILE_FEE_PART: "getMobileFeePart",
|
GET_MOBILE_FEE_PART: "getMobileFeePart",
|
||||||
GET_PRICING_BY_DAY_PART: "getPricingByDayPart",
|
GET_PRICING_BY_DAY_PART: "getPricingByDayPart",
|
||||||
GET_SERVICE_PACKAGE_DISCOUNT_PART: "getServicePackageDiscountPart",
|
|
||||||
GET_SERVICEABILITY_DETAILS: "getServiceabilityDetails",
|
GET_SERVICEABILITY_DETAILS: "getServiceabilityDetails",
|
||||||
GET_SHOP_TIME_SLOTS: "getShopTimeSlots",
|
GET_SHOP_TIME_SLOTS: "getShopTimeSlots",
|
||||||
GET_MOBILE_TIME_SLOTS: "getMobileTimeSlots",
|
GET_MOBILE_TIME_SLOTS: "getMobileTimeSlots",
|
||||||
|
|
@ -111,6 +111,8 @@ const storeActions = {
|
||||||
SAVE_PAYPAL_TOKEN: "savePaypalToken",
|
SAVE_PAYPAL_TOKEN: "savePaypalToken",
|
||||||
SAVE_NEXTGEN_SETTLED_AMOUNT: "saveNextGenSettledAmount",
|
SAVE_NEXTGEN_SETTLED_AMOUNT: "saveNextGenSettledAmount",
|
||||||
SAVE_IS_RECAL_ACK_OPT_IN: "saveIsRecalAckOptIn",
|
SAVE_IS_RECAL_ACK_OPT_IN: "saveIsRecalAckOptIn",
|
||||||
|
SAVE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING: "saveIsRecalAcknowledgedForScheduling",
|
||||||
|
SAVE_IS_OEM_GLASS_SELECTED: "saveIsOemGlassSelected",
|
||||||
SAVE_IS_MSR_FEE_APPLICABLE: "saveIsMSRFeeApplicable",
|
SAVE_IS_MSR_FEE_APPLICABLE: "saveIsMSRFeeApplicable",
|
||||||
|
|
||||||
CREATE_SUBMITTED_STATE: "createSubmittedState",
|
CREATE_SUBMITTED_STATE: "createSubmittedState",
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,8 @@ const storeMutations = {
|
||||||
LOCK_TOKEN: "updateLockToken",
|
LOCK_TOKEN: "updateLockToken",
|
||||||
UPDATE_SETTLED_TENDER_AMOUNT: "updateSettledTenderAmount",
|
UPDATE_SETTLED_TENDER_AMOUNT: "updateSettledTenderAmount",
|
||||||
UPDATE_IS_RECAL_ACK_OPT_IN: "updateIsRecalAckOptIn",
|
UPDATE_IS_RECAL_ACK_OPT_IN: "updateIsRecalAckOptIn",
|
||||||
|
UPDATE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING: "updateIsRecalAcknowledgedForScheduling",
|
||||||
|
UPDATE_IS_OEM_GLASS_SELECTED: "updateIsOemGlassSelected",
|
||||||
UPDATE_IS_MSR_FEE_APPLICABLE: "updateIsMSRFeeApplicable",
|
UPDATE_IS_MSR_FEE_APPLICABLE: "updateIsMSRFeeApplicable",
|
||||||
UPDATE_CASH_PRICE_SUBTOTAL: "updateCashPriceSubTotal",
|
UPDATE_CASH_PRICE_SUBTOTAL: "updateCashPriceSubTotal",
|
||||||
|
|
||||||
|
|
@ -117,6 +119,7 @@ const storeMutations = {
|
||||||
UPDATE_EXTERNAL_PARAMETER_SERVICE_PACKAGE: "updateExternalParameterServicePackage",
|
UPDATE_EXTERNAL_PARAMETER_SERVICE_PACKAGE: "updateExternalParameterServicePackage",
|
||||||
UPDATE_EXTERNAL_PARAMETER_SOURCE: "updateExternalParameterSource",
|
UPDATE_EXTERNAL_PARAMETER_SOURCE: "updateExternalParameterSource",
|
||||||
UPDATE_EXTERNAL_PARAMETER_PHONE_NUMBER: "updateExternalParameterPhoneNumber",
|
UPDATE_EXTERNAL_PARAMETER_PHONE_NUMBER: "updateExternalParameterPhoneNumber",
|
||||||
|
UPDATE_EXTERNAL_PARAMETER_QS_STASH: "updateExternalParameterQsStash",
|
||||||
|
|
||||||
//RESET EXTERNAL_PARAMETER MUTATIONS
|
//RESET EXTERNAL_PARAMETER MUTATIONS
|
||||||
RESET_EXTERNAL_PARAMETER_VEHICLE_STATE: "resetExternalParameterVehicleState",
|
RESET_EXTERNAL_PARAMETER_VEHICLE_STATE: "resetExternalParameterVehicleState",
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,17 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="date-picker text-center"
|
class="date-picker text-center"
|
||||||
:class="`${calendarViewDirection} calendar-2025 ${showPricingByDayClass}`">
|
:class="`${calendarViewDirection} calendar-2025 ${showPricingByDayClass} ${loadingStatus === 'reinitial' ? 'loading-reinitial' : ''}`">
|
||||||
<fieldset id="date-picker-fieldset" ref="datePickerFieldset">
|
<fieldset id="date-picker-fieldset" ref="datePickerFieldset">
|
||||||
<legend class="sr-only">Select a day and time</legend>
|
<legend class="sr-only">Select a day and time</legend>
|
||||||
<div
|
<div
|
||||||
v-for="month in months"
|
v-for="(month, i) in months"
|
||||||
:key="`${month.monthLabel}-${month.yearNum?.toString()}`"
|
:key="`${month.monthLabel}-${month.yearNum?.toString()}`"
|
||||||
:id="`${month.monthLabel}-${month.yearNum?.toString()}`"
|
:id="`${month.monthLabel}-${month.yearNum?.toString()}`"
|
||||||
class="calendar-grid-container"
|
class="calendar-grid-container month"
|
||||||
:class="[
|
:class="[
|
||||||
hideSomeDaysForInitialView ? 'partial-month-initial-view' : '',
|
hideSomeDaysForInitialView ? 'partial-month-initial-view' : '',
|
||||||
month.monthClass,
|
month.monthClass,
|
||||||
|
i === 0 ? 'first-month-shown' : '',
|
||||||
]">
|
]">
|
||||||
<div class="month-year body-small d-flex align-items-center">
|
<div class="month-year body-small d-flex align-items-center">
|
||||||
{{ month.monthLabel }} {{ month.yearNum?.toString() }}
|
{{ month.monthLabel }} {{ month.yearNum?.toString() }}
|
||||||
|
|
@ -114,7 +115,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<loader
|
<loader
|
||||||
:class="[!isLoading ? 'date-picker-hidden' : '']"
|
class="date-picker-loader"
|
||||||
|
:class="[loadingStatus !== 'none' ? 'show-loader' : '']"
|
||||||
loaderColor="blue"
|
loaderColor="blue"
|
||||||
loaderPosition="center" />
|
loaderPosition="center" />
|
||||||
<div class="row form-test-error" id="date-of-month-error">
|
<div class="row form-test-error" id="date-of-month-error">
|
||||||
|
|
@ -125,7 +127,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-link view-more-dates"
|
class="btn btn-link view-more-dates"
|
||||||
id="viewMoreDates"
|
id="viewMoreDates"
|
||||||
:disabled="isLoading"
|
:disabled="loadingStatus !== 'none'"
|
||||||
@click="showAnotherMonth">
|
@click="showAnotherMonth">
|
||||||
View more dates
|
View more dates
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -160,7 +162,6 @@ export default {
|
||||||
name: "datePicker",
|
name: "datePicker",
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
|
||||||
months: null,
|
months: null,
|
||||||
disableViewMoreDatesButton: false,
|
disableViewMoreDatesButton: false,
|
||||||
hideSomeDaysForInitialView: null,
|
hideSomeDaysForInitialView: null,
|
||||||
|
|
@ -169,7 +170,10 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
customComponentId: String,
|
customComponentId: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
selectableDatesSetting: {
|
selectableDatesSetting: {
|
||||||
type: String,
|
type: String,
|
||||||
validator(value) {
|
validator(value) {
|
||||||
|
|
@ -200,6 +204,10 @@ export default {
|
||||||
pricingByDayUpcharge: Number,
|
pricingByDayUpcharge: Number,
|
||||||
isPricingByDayExperiment: Boolean,
|
isPricingByDayExperiment: Boolean,
|
||||||
isMobileSelected: Boolean,
|
isMobileSelected: Boolean,
|
||||||
|
loadingStatus: {
|
||||||
|
type: String,
|
||||||
|
default: "none",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const uuid = uuidv4();
|
const uuid = uuidv4();
|
||||||
|
|
@ -281,7 +289,7 @@ export default {
|
||||||
if (event.screenX === 0 && event.screenY === 0) {
|
if (event.screenX === 0 && event.screenY === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.$emit("date-clicked", date);
|
this.$emit("date-selected", date);
|
||||||
},
|
},
|
||||||
getWeekStartDate(dateString) {
|
getWeekStartDate(dateString) {
|
||||||
const date = convertDateStringToDate(dateString);
|
const date = convertDateStringToDate(dateString);
|
||||||
|
|
@ -324,9 +332,9 @@ export default {
|
||||||
let preSelectedDateMonthEnd = this.getMonthEnd(
|
let preSelectedDateMonthEnd = this.getMonthEnd(
|
||||||
preSelectedDate.replace("-mobile", "")
|
preSelectedDate.replace("-mobile", "")
|
||||||
);
|
);
|
||||||
let weekIncludesPreSelectedMonthEnd = false;
|
let monthEndsByThisWeek = false;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
while (!weekIncludesPreSelectedMonthEnd) {
|
while (!monthEndsByThisWeek && i < 52) {
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
weekStartDate = this.getNextWeekSunday(weekEndDate);
|
weekStartDate = this.getNextWeekSunday(weekEndDate);
|
||||||
weekEndDate = this.getWeekEndDate(weekStartDate);
|
weekEndDate = this.getWeekEndDate(weekStartDate);
|
||||||
|
|
@ -338,7 +346,12 @@ export default {
|
||||||
preSelectedDateMonthEnd === weekEndDate
|
preSelectedDateMonthEnd === weekEndDate
|
||||||
) {
|
) {
|
||||||
weekEndDate = preSelectedDateMonthEnd;
|
weekEndDate = preSelectedDateMonthEnd;
|
||||||
weekIncludesPreSelectedMonthEnd = true;
|
monthEndsByThisWeek = true;
|
||||||
|
} else if (preSelectedDateMonthEnd < weekEndDate) {
|
||||||
|
// Additional case: if month ends before this week (but not necessarily during it),
|
||||||
|
// still cut the loop here, but don't truncate the week.
|
||||||
|
|
||||||
|
monthEndsByThisWeek = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
weeks.push({
|
weeks.push({
|
||||||
|
|
@ -466,7 +479,7 @@ export default {
|
||||||
const response = config.getSelectableDatesCallback({
|
const response = config.getSelectableDatesCallback({
|
||||||
startDateString: initialViewStartDate,
|
startDateString: initialViewStartDate,
|
||||||
endDateString: initialViewEndDate,
|
endDateString: initialViewEndDate,
|
||||||
providerNumber: config.providerNumber,
|
inshopProviderNumber: config.inshopProviderNumber,
|
||||||
zipCode: config.zipCode,
|
zipCode: config.zipCode,
|
||||||
includeMobileTimeSlots: config.includeMobileTimeSlots,
|
includeMobileTimeSlots: config.includeMobileTimeSlots,
|
||||||
includeInshopTimeSlots: config.includeInshopTimeSlots,
|
includeInshopTimeSlots: config.includeInshopTimeSlots,
|
||||||
|
|
@ -489,7 +502,6 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async setCalendarData(config = {}) {
|
async setCalendarData(config = {}) {
|
||||||
this.isLoading = true;
|
|
||||||
this.selectableDatesInshop = [];
|
this.selectableDatesInshop = [];
|
||||||
this.selectableDatesMobile = [];
|
this.selectableDatesMobile = [];
|
||||||
this.months = [];
|
this.months = [];
|
||||||
|
|
@ -541,7 +553,6 @@ export default {
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
this.months = months;
|
this.months = months;
|
||||||
this.isLoading = false;
|
|
||||||
|
|
||||||
if (config.preSelectedDate) {
|
if (config.preSelectedDate) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
|
@ -729,7 +740,6 @@ export default {
|
||||||
},
|
},
|
||||||
async showAnotherMonth() {
|
async showAnotherMonth() {
|
||||||
if (!this.months) return;
|
if (!this.months) return;
|
||||||
this.isLoading = true;
|
|
||||||
let monthToShow;
|
let monthToShow;
|
||||||
let monthStartDate;
|
let monthStartDate;
|
||||||
|
|
||||||
|
|
@ -774,7 +784,6 @@ export default {
|
||||||
monthToShowDates[monthToShowDates.length - 1].inputValue
|
monthToShowDates[monthToShowDates.length - 1].inputValue
|
||||||
);
|
);
|
||||||
|
|
||||||
this.isLoading = false;
|
|
||||||
this.hideSomeDaysForInitialView = false; // if hid days on initial partial view, this will reveal those days
|
this.hideSomeDaysForInitialView = false; // if hid days on initial partial view, this will reveal those days
|
||||||
monthToShow.monthClass = monthToShow.monthClass.replace(" month-hidden", "");
|
monthToShow.monthClass = monthToShow.monthClass.replace(" month-hidden", "");
|
||||||
this.scrollToElement(monthToShow.monthString);
|
this.scrollToElement(monthToShow.monthString);
|
||||||
|
|
@ -787,7 +796,6 @@ export default {
|
||||||
},
|
},
|
||||||
async updateSelectableDates(monthStart, monthEnd) {
|
async updateSelectableDates(monthStart, monthEnd) {
|
||||||
const moreSelectableDates = await this.getMoreDatesCallback(monthStart, monthEnd);
|
const moreSelectableDates = await this.getMoreDatesCallback(monthStart, monthEnd);
|
||||||
|
|
||||||
moreSelectableDates.inshopTimeSlotsData.days.forEach((selectableDate) => {
|
moreSelectableDates.inshopTimeSlotsData.days.forEach((selectableDate) => {
|
||||||
const index = this.selectableDatesInshop.findIndex(
|
const index = this.selectableDatesInshop.findIndex(
|
||||||
(dateObj) => dateObj.date === selectableDate.date
|
(dateObj) => dateObj.date === selectableDate.date
|
||||||
|
|
@ -846,12 +854,6 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
isLoading(newValue) {
|
|
||||||
// if done loading dates, then set to first available date
|
|
||||||
if (newValue === false && this.firstAvailableSelectableDate) {
|
|
||||||
this.selectedDate = this.firstAvailableSelectableDate;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modelValue(newValue) {
|
modelValue(newValue) {
|
||||||
this.resetField({
|
this.resetField({
|
||||||
value: newValue,
|
value: newValue,
|
||||||
|
|
@ -872,9 +874,14 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.date-picker-hidden {
|
.date-picker-loader {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
max-height: 0;
|
max-height: 0;
|
||||||
|
|
||||||
|
&.show-loader {
|
||||||
|
opacity: 1;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.date-picker {
|
.date-picker {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
@ -903,12 +910,12 @@ export default {
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.calendar-grid-container {
|
.month {
|
||||||
margin: 0 auto 1rem auto;
|
margin: 0 auto 1rem auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition:
|
transition:
|
||||||
height ease 2s,
|
height ease 1s,
|
||||||
opacity ease 2s;
|
max-height ease 1s;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(7, 1fr);
|
grid-template-columns: repeat(7, 1fr);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
@ -1157,7 +1164,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.past {
|
.past {
|
||||||
.calendar-grid-container {
|
.month {
|
||||||
.month-year {
|
.month-year {
|
||||||
grid-area: 1 / 1 / 2 / 6;
|
grid-area: 1 / 1 / 2 / 6;
|
||||||
}
|
}
|
||||||
|
|
@ -1214,7 +1221,7 @@ export default {
|
||||||
}
|
}
|
||||||
// new calendar styling overrides
|
// new calendar styling overrides
|
||||||
&.calendar-2025 {
|
&.calendar-2025 {
|
||||||
.calendar-grid-container .grid-item {
|
.month .grid-item {
|
||||||
margin: 0 0 0.15rem 0;
|
margin: 0 0 0.15rem 0;
|
||||||
}
|
}
|
||||||
.month-year {
|
.month-year {
|
||||||
|
|
@ -1316,6 +1323,33 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.loading-reinitial {
|
||||||
|
.month {
|
||||||
|
opacity: 0;
|
||||||
|
max-height: 0;
|
||||||
|
}
|
||||||
|
.month.first-month-shown {
|
||||||
|
opacity: 1;
|
||||||
|
max-height: none;
|
||||||
|
|
||||||
|
.grid-item.radio-wrapper.selectable-day {
|
||||||
|
input[type="radio"]:checked + label,
|
||||||
|
label {
|
||||||
|
background-color: $white;
|
||||||
|
border: none;
|
||||||
|
color: initial;
|
||||||
|
cursor: default;
|
||||||
|
&:hover {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
font-family: UrbanistRegular;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
:deep(.timeslots .button-inner-wrapper) {
|
:deep(.timeslots .button-inner-wrapper) {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,10 @@ export default {
|
||||||
return this.staticBackdrop ? "static" : "true";
|
return this.staticBackdrop ? "static" : "true";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
unmounted() {
|
||||||
|
// remove any modal effects before leaving page (e.g. user hits browser back button)
|
||||||
|
this.closeModal();
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
modalButtonMain,
|
modalButtonMain,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
// ***************************************************************
|
||||||
|
// This file wraps the Salesforce generated code.
|
||||||
|
// See the comment below for where you need to paste in the Salesforce generated code.
|
||||||
|
// You may also need to modify the script tag src located in salesforce-webchat.vue to point to the correct Salesforce instance.
|
||||||
|
// ***************************************************************
|
||||||
|
|
||||||
function setupCustomSettings(
|
function setupCustomSettings(
|
||||||
modifyEmbeddedSvcCallback,
|
modifyEmbeddedSvcCallback,
|
||||||
|
|
@ -7,90 +12,38 @@ function setupCustomSettings(
|
||||||
onWebchatCloseCallback
|
onWebchatCloseCallback
|
||||||
) {
|
) {
|
||||||
modifyEmbeddedSvcCallback();
|
modifyEmbeddedSvcCallback();
|
||||||
embedded_svc.addEventHandler("onSettingsCallCompleted", finishedLoadingCallback);
|
window.addEventListener("onEmbeddedMessagingButtonCreated", finishedLoadingCallback);
|
||||||
embedded_svc.addEventHandler("afterMaximize", onWebchatOpenCallback);
|
window.addEventListener("onEmbeddedMessagingWindowMaximized", onWebchatOpenCallback);
|
||||||
embedded_svc.addEventHandler("afterDestroy", onWebchatCloseCallback);
|
window.addEventListener("onEmbeddedMessagingWindowClosed", onWebchatCloseCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
The code below is generated from salesforce but modified in the following ways:
|
|
||||||
* Only the javascript inside the second <script> tag in the provided salesforce code is copy pasted
|
|
||||||
* CSS is not copied in
|
|
||||||
* First <script> tag loading in the file from the CDN is not copied in
|
|
||||||
* Copy the entire contents of the second <script> tag inside the initializeSalesforceWebchatForDev
|
|
||||||
method and add a call to setupCustomSettings before the 'embedded_svc.init()' method is called
|
|
||||||
|
|
||||||
Actual call to paste in:
|
|
||||||
setupCustomSettings(
|
|
||||||
modifyEmbeddedSvcCallback,
|
|
||||||
finishedLoadingCallback,
|
|
||||||
onWebchatOpenCallback,
|
|
||||||
onWebchatCloseCallback
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function initializeSalesforceWebchatForDev(
|
export function initializeSalesforceWebchatForDev(
|
||||||
modifyEmbeddedSvcCallback,
|
modifyEmbeddedSvcCallback,
|
||||||
finishedLoadingCallback,
|
finishedLoadingCallback,
|
||||||
onWebchatOpenCallback,
|
onWebchatOpenCallback,
|
||||||
onWebchatCloseCallback
|
onWebchatCloseCallback
|
||||||
) {
|
) {
|
||||||
var initESW = function (gslbBaseURL) {
|
setupCustomSettings(
|
||||||
embedded_svc.settings.displayHelpButton = true; //Or false
|
modifyEmbeddedSvcCallback,
|
||||||
embedded_svc.settings.language = ""; //For example, enter 'en' or 'en-US'
|
finishedLoadingCallback,
|
||||||
//embedded_svc.settings.defaultMinimizedText = '...'; //(Defaults to Chat with an Expert)
|
onWebchatOpenCallback,
|
||||||
//embedded_svc.settings.disabledMinimizedText = '...'; //(Defaults to Agent Offline)
|
onWebchatCloseCallback
|
||||||
//embedded_svc.settings.loadingText = ''; //(Defaults to Loading)
|
);
|
||||||
//embedded_svc.settings.storageDomain = 'yourdomain.com'; //(Sets the domain for your deployment so that visitors can navigate subdomains during a chat session)
|
// START OF SALESFORCE GENERATED CODE
|
||||||
// Settings for Chat
|
// Copy in the contents of the first <script> tag inside the initEmbeddedMessaging() method
|
||||||
//embedded_svc.settings.directToButtonRouting = function(prechatFormData) {
|
try {
|
||||||
// Dynamically changes the button ID based on what the visitor enters in the pre-chat form.
|
embeddedservice_bootstrap.settings.language = "en_US"; // For example, enter 'en' or 'en-US'
|
||||||
// Returns a valid button ID.
|
|
||||||
//};
|
|
||||||
//embedded_svc.settings.prepopulatedPrechatFields = {}; //Sets the auto-population of pre-chat form fields
|
|
||||||
//embedded_svc.settings.fallbackRouting = []; //An array of button IDs, user IDs, or userId_buttonId
|
|
||||||
//embedded_svc.settings.offlineSupportMinimizedText = '...'; //(Defaults to Contact Us)
|
|
||||||
|
|
||||||
embedded_svc.settings.enabledFeatures = ["LiveAgent"];
|
embeddedservice_bootstrap.init(
|
||||||
embedded_svc.settings.entryFeature = "LiveAgent";
|
|
||||||
|
|
||||||
setupCustomSettings(
|
|
||||||
modifyEmbeddedSvcCallback,
|
|
||||||
finishedLoadingCallback,
|
|
||||||
onWebchatOpenCallback,
|
|
||||||
onWebchatCloseCallback
|
|
||||||
);
|
|
||||||
|
|
||||||
embedded_svc.init(
|
|
||||||
"https://safelite2--dev.sandbox.my.salesforce.com",
|
|
||||||
"https://safelite2--dev.sandbox.my.salesforce-sites.com/chat",
|
|
||||||
gslbBaseURL,
|
|
||||||
"00D6s0000008r6A",
|
"00D6s0000008r6A",
|
||||||
"Sales_Central_Snap_in",
|
"Sales_Central_TFN",
|
||||||
|
"https://safelite2--dev.sandbox.my.site.com/ESWSalesCentralTFN1755660094477",
|
||||||
{
|
{
|
||||||
baseLiveAgentContentURL:
|
scrt2URL: "https://safelite2--dev.sandbox.my.salesforce-scrt.com",
|
||||||
"https://c.la12s-core1.sfdc-8tgtt5.salesforceliveagent.com/content",
|
|
||||||
deploymentId: "572Wr0000017xKX",
|
|
||||||
buttonId: "573Wr000000CpgH",
|
|
||||||
baseLiveAgentURL: "https://d.la12s-core1.sfdc-8tgtt5.salesforceliveagent.com/chat",
|
|
||||||
eswLiveAgentDevName:
|
|
||||||
"EmbeddedServiceLiveAgent_Parent04IWr000003yKq9MAE_1961681659a",
|
|
||||||
isOfflineSupportEnabled: false,
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
} catch (err) {
|
||||||
|
console.error("Error loading Embedded Messaging: ", err);
|
||||||
if (!window.embedded_svc) {
|
|
||||||
var s = document.createElement("script");
|
|
||||||
s.setAttribute(
|
|
||||||
"src",
|
|
||||||
"https://safelite2--dev.sandbox.my.salesforce.com/embeddedservice/5.0/esw.min.js"
|
|
||||||
);
|
|
||||||
s.onload = function () {
|
|
||||||
initESW(null);
|
|
||||||
};
|
|
||||||
document.body.appendChild(s);
|
|
||||||
} else {
|
|
||||||
initESW("https://service.force.com");
|
|
||||||
}
|
}
|
||||||
|
// END OF SALESFORCE GENERATED CODE
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
// ***************************************************************
|
||||||
|
// This file wraps the Salesforce generated code.
|
||||||
|
// See the comment below for where you need to paste in the Salesforce generated code.
|
||||||
|
// You may also need to modify the script tag src located in salesforce-webchat.vue to point to the correct Salesforce instance.
|
||||||
|
// ***************************************************************
|
||||||
|
|
||||||
function setupCustomSettings(
|
function setupCustomSettings(
|
||||||
modifyEmbeddedSvcCallback,
|
modifyEmbeddedSvcCallback,
|
||||||
|
|
@ -7,91 +12,39 @@ function setupCustomSettings(
|
||||||
onWebchatCloseCallback
|
onWebchatCloseCallback
|
||||||
) {
|
) {
|
||||||
modifyEmbeddedSvcCallback();
|
modifyEmbeddedSvcCallback();
|
||||||
embedded_svc.addEventHandler("onSettingsCallCompleted", finishedLoadingCallback);
|
window.addEventListener("onEmbeddedMessagingButtonCreated", finishedLoadingCallback);
|
||||||
embedded_svc.addEventHandler("afterMaximize", onWebchatOpenCallback);
|
window.addEventListener("onEmbeddedMessagingWindowMaximized", onWebchatOpenCallback);
|
||||||
embedded_svc.addEventHandler("afterDestroy", onWebchatCloseCallback);
|
window.addEventListener("onEmbeddedMessagingWindowClosed", onWebchatCloseCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
The code below is generated from salesforce but modified in the following ways:
|
|
||||||
* Only the javascript inside the second <script> tag in the provided salesforce code is copy pasted
|
|
||||||
* CSS is not copied in
|
|
||||||
* First <script> tag loading in the file from the CDN is not copied in
|
|
||||||
* Copy the entire contents of the second <script> tag inside the initializeSalesforceWebchatForDev
|
|
||||||
method and add a call to setupCustomSettings before the 'embedded_svc.init()' method is called
|
|
||||||
|
|
||||||
Actual call to paste in:
|
|
||||||
setupCustomSettings(
|
|
||||||
modifyEmbeddedSvcCallback,
|
|
||||||
finishedLoadingCallback,
|
|
||||||
onWebchatOpenCallback,
|
|
||||||
onWebchatCloseCallback
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function initializeSalesforceWebchatForProd(
|
export function initializeSalesforceWebchatForProd(
|
||||||
modifyEmbeddedSvcCallback,
|
modifyEmbeddedSvcCallback,
|
||||||
finishedLoadingCallback,
|
finishedLoadingCallback,
|
||||||
onWebchatOpenCallback,
|
onWebchatOpenCallback,
|
||||||
onWebchatCloseCallback
|
onWebchatCloseCallback
|
||||||
) {
|
) {
|
||||||
var initESW = function (gslbBaseURL) {
|
setupCustomSettings(
|
||||||
let embedded_svc = window.embedded_svc;
|
modifyEmbeddedSvcCallback,
|
||||||
embedded_svc.settings.displayHelpButton = true; //Or false
|
finishedLoadingCallback,
|
||||||
embedded_svc.settings.language = ""; //For example, enter 'en' or 'en-US'
|
onWebchatOpenCallback,
|
||||||
|
onWebchatCloseCallback
|
||||||
|
);
|
||||||
|
|
||||||
//embedded_svc.settings.defaultMinimizedText = '...'; //(Defaults to Chat with an Expert)
|
// START OF SALESFORCE GENERATED CODE
|
||||||
//embedded_svc.settings.disabledMinimizedText = '...'; //(Defaults to Agent Offline)
|
// Copy in the contents of the first <script> tag inside the initEmbeddedMessaging() method
|
||||||
|
try {
|
||||||
|
embeddedservice_bootstrap.settings.language = "en_US"; // For example, enter 'en' or 'en-US'
|
||||||
|
|
||||||
//embedded_svc.settings.loadingText = ''; //(Defaults to Loading)
|
embeddedservice_bootstrap.init(
|
||||||
//embedded_svc.settings.storageDomain = 'yourdomain.com'; //(Sets the domain for your deployment so that visitors can navigate subdomains during a chat session)
|
|
||||||
|
|
||||||
// Settings for Chat
|
|
||||||
//embedded_svc.settings.directToButtonRouting = function(prechatFormData) {
|
|
||||||
// Dynamically changes the button ID based on what the visitor enters in the pre-chat form.
|
|
||||||
// Returns a valid button ID.
|
|
||||||
//};
|
|
||||||
//embedded_svc.settings.prepopulatedPrechatFields = {}; //Sets the auto-population of pre-chat form fields
|
|
||||||
//embedded_svc.settings.fallbackRouting = []; //An array of button IDs, user IDs, or userId_buttonId
|
|
||||||
//embedded_svc.settings.offlineSupportMinimizedText = '...'; //(Defaults to Contact Us)
|
|
||||||
|
|
||||||
embedded_svc.settings.enabledFeatures = ["LiveAgent"];
|
|
||||||
embedded_svc.settings.entryFeature = "LiveAgent";
|
|
||||||
|
|
||||||
setupCustomSettings(
|
|
||||||
modifyEmbeddedSvcCallback,
|
|
||||||
finishedLoadingCallback,
|
|
||||||
onWebchatOpenCallback,
|
|
||||||
onWebchatCloseCallback
|
|
||||||
);
|
|
||||||
|
|
||||||
embedded_svc.init(
|
|
||||||
"https://safelite2.my.salesforce.com",
|
|
||||||
"https://safelite2.my.salesforce-sites.com/chat",
|
|
||||||
gslbBaseURL,
|
|
||||||
"00D3t000004nvsT",
|
"00D3t000004nvsT",
|
||||||
"Sales_Central_Snap_in",
|
"Sales_Central_TFN",
|
||||||
|
"https://safelite2.my.site.com/ESWSalesCentralTFN1769108630294",
|
||||||
{
|
{
|
||||||
baseLiveAgentContentURL:
|
scrt2URL: "https://safelite2.my.salesforce-scrt.com",
|
||||||
"https://c.la13-core2.sfdc-lywfpd.salesforceliveagent.com/content",
|
|
||||||
deploymentId: "5723t0000008wWB",
|
|
||||||
buttonId: "5733t000000D90z",
|
|
||||||
baseLiveAgentURL: "https://d.la13-core2.sfdc-lywfpd.salesforceliveagent.com/chat",
|
|
||||||
eswLiveAgentDevName:
|
|
||||||
"EmbeddedServiceLiveAgent_Parent04I3t000000Cq0SEAS_188de1871ea",
|
|
||||||
isOfflineSupportEnabled: false,
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
} catch (err) {
|
||||||
|
console.error("Error loading Embedded Messaging: ", err);
|
||||||
if (!window.embedded_svc) {
|
|
||||||
var s = document.createElement("script");
|
|
||||||
s.setAttribute("src", "https://safelite2.my.salesforce.com/embeddedservice/5.0/esw.min.js");
|
|
||||||
s.onload = function () {
|
|
||||||
initESW(null);
|
|
||||||
};
|
|
||||||
document.body.appendChild(s);
|
|
||||||
} else {
|
|
||||||
initESW("https://service.force.com");
|
|
||||||
}
|
}
|
||||||
|
// END OF SALESFORCE GENERATED CODE
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
// ***************************************************************
|
||||||
|
// This file wraps the Salesforce generated code.
|
||||||
|
// See the comment below for where you need to paste in the Salesforce generated code.
|
||||||
|
// You may also need to modify the script tag src located in salesforce-webchat.vue to point to the correct Salesforce instance.
|
||||||
|
// ***************************************************************
|
||||||
|
|
||||||
function setupCustomSettings(
|
function setupCustomSettings(
|
||||||
modifyEmbeddedSvcCallback,
|
modifyEmbeddedSvcCallback,
|
||||||
|
|
@ -7,93 +12,38 @@ function setupCustomSettings(
|
||||||
onWebchatCloseCallback
|
onWebchatCloseCallback
|
||||||
) {
|
) {
|
||||||
modifyEmbeddedSvcCallback();
|
modifyEmbeddedSvcCallback();
|
||||||
embedded_svc.addEventHandler("onSettingsCallCompleted", finishedLoadingCallback);
|
window.addEventListener("onEmbeddedMessagingButtonCreated", finishedLoadingCallback);
|
||||||
embedded_svc.addEventHandler("afterMaximize", onWebchatOpenCallback);
|
window.addEventListener("onEmbeddedMessagingWindowMaximized", onWebchatOpenCallback);
|
||||||
embedded_svc.addEventHandler("afterDestroy", onWebchatCloseCallback);
|
window.addEventListener("onEmbeddedMessagingWindowClosed", onWebchatCloseCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
The code below is generated from salesforce but modified in the following ways:
|
|
||||||
* Only the javascript inside the second <script> tag in the provided salesforce code is copy pasted
|
|
||||||
* CSS is not copied in
|
|
||||||
* First <script> tag loading in the file from the CDN is not copied in
|
|
||||||
* Copy the entire contents of the second <script> tag inside the initializeSalesforceWebchatForQa
|
|
||||||
method and add a call to setupCustomSettings before the 'embedded_svc.init()' method is called
|
|
||||||
|
|
||||||
Actual call to paste in:
|
|
||||||
setupCustomSettings(
|
|
||||||
modifyEmbeddedSvcCallback,
|
|
||||||
finishedLoadingCallback,
|
|
||||||
onWebchatOpenCallback,
|
|
||||||
onWebchatCloseCallback
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function initializeSalesforceWebchatForQa(
|
export function initializeSalesforceWebchatForQa(
|
||||||
modifyEmbeddedSvcCallback,
|
modifyEmbeddedSvcCallback,
|
||||||
finishedLoadingCallback,
|
finishedLoadingCallback,
|
||||||
onWebchatOpenCallback,
|
onWebchatOpenCallback,
|
||||||
onWebchatCloseCallback
|
onWebchatCloseCallback
|
||||||
) {
|
) {
|
||||||
var initESW = function (gslbBaseURL) {
|
setupCustomSettings(
|
||||||
embedded_svc.settings.displayHelpButton = true; //Or false
|
modifyEmbeddedSvcCallback,
|
||||||
embedded_svc.settings.language = ""; //For example, enter 'en' or 'en-US'
|
finishedLoadingCallback,
|
||||||
|
onWebchatOpenCallback,
|
||||||
|
onWebchatCloseCallback
|
||||||
|
);
|
||||||
|
// START OF SALESFORCE GENERATED CODE
|
||||||
|
// Copy in the contents of the first <script> tag inside the initEmbeddedMessaging() method
|
||||||
|
try {
|
||||||
|
embeddedservice_bootstrap.settings.language = "en_US"; // For example, enter 'en' or 'en-US'
|
||||||
|
|
||||||
//embedded_svc.settings.defaultMinimizedText = '...'; //(Defaults to Chat with an Expert)
|
embeddedservice_bootstrap.init(
|
||||||
//embedded_svc.settings.disabledMinimizedText = '...'; //(Defaults to Agent Offline)
|
"00DTI000005x7c7",
|
||||||
|
"Sales_Central_TFN",
|
||||||
//embedded_svc.settings.loadingText = ''; //(Defaults to Loading)
|
"https://safelite2--qa.sandbox.my.site.com/ESWSalesCentralTFN1768832680884",
|
||||||
//embedded_svc.settings.storageDomain = 'yourdomain.com'; //(Sets the domain for your deployment so that visitors can navigate subdomains during a chat session)
|
|
||||||
|
|
||||||
// Settings for Chat
|
|
||||||
//embedded_svc.settings.directToButtonRouting = function(prechatFormData) {
|
|
||||||
// Dynamically changes the button ID based on what the visitor enters in the pre-chat form.
|
|
||||||
// Returns a valid button ID.
|
|
||||||
//};
|
|
||||||
//embedded_svc.settings.prepopulatedPrechatFields = {}; //Sets the auto-population of pre-chat form fields
|
|
||||||
//embedded_svc.settings.fallbackRouting = []; //An array of button IDs, user IDs, or userId_buttonId
|
|
||||||
//embedded_svc.settings.offlineSupportMinimizedText = '...'; //(Defaults to Contact Us)
|
|
||||||
|
|
||||||
embedded_svc.settings.enabledFeatures = ["LiveAgent"];
|
|
||||||
embedded_svc.settings.entryFeature = "LiveAgent";
|
|
||||||
|
|
||||||
setupCustomSettings(
|
|
||||||
modifyEmbeddedSvcCallback,
|
|
||||||
finishedLoadingCallback,
|
|
||||||
onWebchatOpenCallback,
|
|
||||||
onWebchatCloseCallback
|
|
||||||
);
|
|
||||||
|
|
||||||
embedded_svc.init(
|
|
||||||
"https://safelite2--safeliteua.sandbox.my.salesforce.com",
|
|
||||||
"https://safelite2--safeliteua.sandbox.my.salesforce-sites.com/chat",
|
|
||||||
gslbBaseURL,
|
|
||||||
"00DDJ000000HRqO",
|
|
||||||
"Sales_Central_Snap_in",
|
|
||||||
{
|
{
|
||||||
baseLiveAgentContentURL:
|
scrt2URL: "https://safelite2--qa.sandbox.my.salesforce-scrt.com",
|
||||||
"https://c.la12s-core1.sfdc-8tgtt5.salesforceliveagent.com/content",
|
|
||||||
deploymentId: "572DJ00000001j3",
|
|
||||||
buttonId: "573DJ00000001ur",
|
|
||||||
baseLiveAgentURL: "https://d.la12s-core1.sfdc-8tgtt5.salesforceliveagent.com/chat",
|
|
||||||
eswLiveAgentDevName:
|
|
||||||
"EmbeddedServiceLiveAgent_Parent04IDJ0000008OZk2AM_1883057c9a6",
|
|
||||||
isOfflineSupportEnabled: false,
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
} catch (err) {
|
||||||
|
console.error("Error loading Embedded Messaging: ", err);
|
||||||
if (!window.embedded_svc) {
|
|
||||||
var s = document.createElement("script");
|
|
||||||
s.setAttribute(
|
|
||||||
"src",
|
|
||||||
"https://safelite2--safeliteua.sandbox.my.salesforce.com/embeddedservice/5.0/esw.min.js"
|
|
||||||
);
|
|
||||||
s.onload = function () {
|
|
||||||
initESW(null);
|
|
||||||
};
|
|
||||||
document.body.appendChild(s);
|
|
||||||
} else {
|
|
||||||
initESW("https://service.force.com");
|
|
||||||
}
|
}
|
||||||
|
// END OF SALESFORCE GENERATED CODE
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
// ***************************************************************
|
||||||
|
// This file wraps the Salesforce generated code.
|
||||||
|
// See the comment below for where you need to paste in the Salesforce generated code.
|
||||||
|
// You may also need to modify the script tag src located in salesforce-webchat.vue to point to the correct Salesforce instance.
|
||||||
|
// ***************************************************************
|
||||||
|
|
||||||
|
function setupCustomSettings(
|
||||||
|
modifyEmbeddedSvcCallback,
|
||||||
|
finishedLoadingCallback,
|
||||||
|
onWebchatOpenCallback,
|
||||||
|
onWebchatCloseCallback
|
||||||
|
) {
|
||||||
|
modifyEmbeddedSvcCallback();
|
||||||
|
window.addEventListener("onEmbeddedMessagingButtonCreated", finishedLoadingCallback);
|
||||||
|
window.addEventListener("onEmbeddedMessagingWindowMaximized", onWebchatOpenCallback);
|
||||||
|
window.addEventListener("onEmbeddedMessagingWindowClosed", onWebchatCloseCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initializeSalesforceWebchatForSys(
|
||||||
|
modifyEmbeddedSvcCallback,
|
||||||
|
finishedLoadingCallback,
|
||||||
|
onWebchatOpenCallback,
|
||||||
|
onWebchatCloseCallback
|
||||||
|
) {
|
||||||
|
setupCustomSettings(
|
||||||
|
modifyEmbeddedSvcCallback,
|
||||||
|
finishedLoadingCallback,
|
||||||
|
onWebchatOpenCallback,
|
||||||
|
onWebchatCloseCallback
|
||||||
|
);
|
||||||
|
|
||||||
|
// START OF SALESFORCE GENERATED CODE
|
||||||
|
// Copy in the contents of the first <script> tag inside the initEmbeddedMessaging() method
|
||||||
|
try {
|
||||||
|
embeddedservice_bootstrap.settings.language = "en_US"; // For example, enter 'en' or 'en-US'
|
||||||
|
|
||||||
|
embeddedservice_bootstrap.init(
|
||||||
|
"00DcY0000067qKz",
|
||||||
|
"Sales_Central_TFN",
|
||||||
|
"https://safelite2--sys.sandbox.my.site.com/ESWSalesCentralTFN1767797265631",
|
||||||
|
{
|
||||||
|
scrt2URL: "https://safelite2--sys.sandbox.my.salesforce-scrt.com",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error loading Embedded Messaging: ", err);
|
||||||
|
}
|
||||||
|
// END OF SALESFORCE GENERATED CODE
|
||||||
|
}
|
||||||
|
|
@ -2,10 +2,20 @@ import { shallowMount } from "@vue/test-utils";
|
||||||
import salesforceWebchat from "./salesforce-webchat.vue";
|
import salesforceWebchat from "./salesforce-webchat.vue";
|
||||||
import * as devHelper from "./salesforce-helper-dev";
|
import * as devHelper from "./salesforce-helper-dev";
|
||||||
import * as qaHelper from "./salesforce-helper-qa";
|
import * as qaHelper from "./salesforce-helper-qa";
|
||||||
|
import * as sysHelper from "./salesforce-helper-sys";
|
||||||
import * as prodHelper from "./salesforce-helper-prod";
|
import * as prodHelper from "./salesforce-helper-prod";
|
||||||
import { applicationConfig } from "../../constants/application-config";
|
import { applicationConfig } from "../../constants/application-config";
|
||||||
|
|
||||||
describe("salesforceWebchat.vue", () => {
|
describe("salesforceWebchat.vue", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
window.embeddedservice_bootstrap = {
|
||||||
|
settings: {},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
delete window.embeddedservice_bootstrap;
|
||||||
|
});
|
||||||
it("renders correctly", () => {
|
it("renders correctly", () => {
|
||||||
const wrapper = shallowMount(salesforceWebchat);
|
const wrapper = shallowMount(salesforceWebchat);
|
||||||
expect(wrapper.exists()).toBe(true);
|
expect(wrapper.exists()).toBe(true);
|
||||||
|
|
@ -14,7 +24,7 @@ describe("salesforceWebchat.vue", () => {
|
||||||
it("has the correct default data", () => {
|
it("has the correct default data", () => {
|
||||||
const wrapper = shallowMount(salesforceWebchat);
|
const wrapper = shallowMount(salesforceWebchat);
|
||||||
expect(wrapper.vm.$data).toEqual({
|
expect(wrapper.vm.$data).toEqual({
|
||||||
isAgentAvailable: false,
|
isWebchatLoaded: false,
|
||||||
isWebchatOpen: false,
|
isWebchatOpen: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -37,6 +47,12 @@ describe("salesforceWebchat.vue", () => {
|
||||||
wrapper.vm.initializeSalesforceWebchat();
|
wrapper.vm.initializeSalesforceWebchat();
|
||||||
expect(mockQa).toHaveBeenCalled();
|
expect(mockQa).toHaveBeenCalled();
|
||||||
|
|
||||||
|
applicationConfig.CURRENT_ENVIRONMENT = "SysTest";
|
||||||
|
wrapper = shallowMount(salesforceWebchat);
|
||||||
|
const mockSys = jest.spyOn(sysHelper, "initializeSalesforceWebchatForSys");
|
||||||
|
wrapper.vm.initializeSalesforceWebchat();
|
||||||
|
expect(mockSys).toHaveBeenCalled();
|
||||||
|
|
||||||
applicationConfig.CURRENT_ENVIRONMENT = "Dev";
|
applicationConfig.CURRENT_ENVIRONMENT = "Dev";
|
||||||
wrapper = shallowMount(salesforceWebchat);
|
wrapper = shallowMount(salesforceWebchat);
|
||||||
const mockDev = jest.spyOn(devHelper, "initializeSalesforceWebchatForDev");
|
const mockDev = jest.spyOn(devHelper, "initializeSalesforceWebchatForDev");
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
// Supporting files
|
// Supporting files
|
||||||
import { initializeSalesforceWebchatForDev } from "./salesforce-helper-dev";
|
import { initializeSalesforceWebchatForDev } from "./salesforce-helper-dev";
|
||||||
import { initializeSalesforceWebchatForQa } from "./salesforce-helper-qa";
|
import { initializeSalesforceWebchatForQa } from "./salesforce-helper-qa";
|
||||||
|
import { initializeSalesforceWebchatForSys } from "./salesforce-helper-sys";
|
||||||
import { initializeSalesforceWebchatForProd } from "./salesforce-helper-prod";
|
import { initializeSalesforceWebchatForProd } from "./salesforce-helper-prod";
|
||||||
import { applicationConfig } from "@/constants/application-config";
|
import { applicationConfig } from "@/constants/application-config";
|
||||||
import { webchatHelper } from "@/helpers/webchat-helper";
|
import { webchatHelper } from "@/helpers/webchat-helper";
|
||||||
|
|
@ -16,7 +17,7 @@ export default {
|
||||||
mixins: [analyticsMixin],
|
mixins: [analyticsMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isAgentAvailable: false,
|
isWebchatLoaded: false,
|
||||||
isWebchatOpen: false,
|
isWebchatOpen: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -33,32 +34,13 @@ export default {
|
||||||
mounted() {
|
mounted() {
|
||||||
// Load in script from salesforce CDN
|
// Load in script from salesforce CDN
|
||||||
const script = document.createElement("script");
|
const script = document.createElement("script");
|
||||||
script.src = "https://service.force.com/embeddedservice/5.0/esw.min.js";
|
script.src = this.webChatScriptSrc;
|
||||||
script.onload = this.initializeSalesforceWebchat;
|
script.onload = this.initializeSalesforceWebchat;
|
||||||
document.body.appendChild(script);
|
document.body.appendChild(script);
|
||||||
|
|
||||||
// Notify funnel-header about Salesforce chat open/close state
|
|
||||||
this._salesforceChatOpenObserver = new MutationObserver(() => {
|
|
||||||
const isOpen = !!document.querySelector(".embeddedServiceSidebar");
|
|
||||||
window.dispatchEvent(
|
|
||||||
new CustomEvent("salesforce-chat-visibility", { detail: { open: isOpen } })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
this._salesforceChatOpenObserver.observe(document.body, {
|
|
||||||
childList: true,
|
|
||||||
subtree: true,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
// Clean up event listeners and observers
|
// Clean up event listeners and observers
|
||||||
window.removeEventListener(
|
|
||||||
"salesforce-chat-visibility",
|
|
||||||
this._handleSalesforceChatVisibility
|
|
||||||
);
|
|
||||||
window.removeEventListener("sierra-chat-transfer", this._handleSierraToSalesforceTransfer);
|
window.removeEventListener("sierra-chat-transfer", this._handleSierraToSalesforceTransfer);
|
||||||
if (this._salesforceChatOpenObserver) {
|
|
||||||
this._salesforceChatOpenObserver.disconnect();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initializeSalesforceWebchat() {
|
initializeSalesforceWebchat() {
|
||||||
|
|
@ -72,7 +54,6 @@ export default {
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "QA":
|
case "QA":
|
||||||
case "SysTest":
|
|
||||||
initializeSalesforceWebchatForQa(
|
initializeSalesforceWebchatForQa(
|
||||||
this.modifyEmbeddedSvcCallback,
|
this.modifyEmbeddedSvcCallback,
|
||||||
this.finishedLoadingCallback,
|
this.finishedLoadingCallback,
|
||||||
|
|
@ -80,8 +61,17 @@ export default {
|
||||||
this.onWebchatCloseCallback
|
this.onWebchatCloseCallback
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case "SysTest":
|
||||||
|
initializeSalesforceWebchatForSys(
|
||||||
|
this.modifyEmbeddedSvcCallback,
|
||||||
|
this.finishedLoadingCallback,
|
||||||
|
this.onWebchatOpenCallback,
|
||||||
|
this.onWebchatCloseCallback
|
||||||
|
);
|
||||||
|
break;
|
||||||
case "Dev":
|
case "Dev":
|
||||||
case "Localhost":
|
case "Localhost":
|
||||||
|
default:
|
||||||
initializeSalesforceWebchatForDev(
|
initializeSalesforceWebchatForDev(
|
||||||
this.modifyEmbeddedSvcCallback,
|
this.modifyEmbeddedSvcCallback,
|
||||||
this.finishedLoadingCallback,
|
this.finishedLoadingCallback,
|
||||||
|
|
@ -92,16 +82,13 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifyEmbeddedSvcCallback() {
|
modifyEmbeddedSvcCallback() {
|
||||||
window.embedded_svc.settings.avatarImgURL = require("@/assets/img/salesforce-webchat/avatar_webchat.png");
|
window.embeddedservice_bootstrap.settings.hideChatButtonOnLoad = true;
|
||||||
window.embedded_svc.settings.prechatBackgroundImgURL = require("@/assets/img/salesforce-webchat/safelite_logo_webchat.png");
|
window.addEventListener("onEmbeddedMessagingConversationStarted", () => {
|
||||||
window.embedded_svc.settings.smallCompanyLogoImgURL = require("@/assets/img/salesforce-webchat/minimized_chat_bubble_webchat.png");
|
|
||||||
window.embedded_svc.settings.displayHelpButton = false;
|
|
||||||
window.embedded_svc.addEventHandler("onChatEstablished", () => {
|
|
||||||
this.pushEventForChatsToGA("support_chat", "chat_opened", "Live Agent", true);
|
this.pushEventForChatsToGA("support_chat", "chat_opened", "Live Agent", true);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
finishedLoadingCallback(data) {
|
finishedLoadingCallback(data) {
|
||||||
this.isAgentAvailable = data.isAgentAvailable;
|
this.isWebchatLoaded = true;
|
||||||
this.updateShouldShowWebchatButton();
|
this.updateShouldShowWebchatButton();
|
||||||
},
|
},
|
||||||
onWebchatOpenCallback() {
|
onWebchatOpenCallback() {
|
||||||
|
|
@ -117,16 +104,35 @@ export default {
|
||||||
updateShouldShowWebchatButton() {
|
updateShouldShowWebchatButton() {
|
||||||
this.webchatGlobalNonpersistedState.showWebchatButton =
|
this.webchatGlobalNonpersistedState.showWebchatButton =
|
||||||
!this.hideSalesforceWebchatLaunchButton &&
|
!this.hideSalesforceWebchatLaunchButton &&
|
||||||
this.isAgentAvailable &&
|
this.isWebchatLoaded &&
|
||||||
!this.isWebchatOpen;
|
!this.isWebchatOpen;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {},
|
computed: {
|
||||||
|
webChatScriptSrc() {
|
||||||
|
// These URLs are from the src tag on the script tag in the Salesforce generated code.
|
||||||
|
switch (applicationConfig.CURRENT_ENVIRONMENT) {
|
||||||
|
case "Prod":
|
||||||
|
return "https://safelite2.my.site.com/ESWSalesCentralTFN1769108630294/assets/js/bootstrap.min.js";
|
||||||
|
case "QA":
|
||||||
|
return "https://safelite2--qa.sandbox.my.site.com/ESWSalesCentralTFN1768832680884/assets/js/bootstrap.min.js";
|
||||||
|
case "SysTest":
|
||||||
|
return "https://safelite2--sys.sandbox.my.site.com/ESWSalesCentralTFN1767797265631/assets/js/bootstrap.min.js";
|
||||||
|
case "Dev":
|
||||||
|
case "Localhost":
|
||||||
|
default:
|
||||||
|
return "https://safelite2--dev.sandbox.my.site.com/ESWSalesCentralTFN1755660094477/assets/js/bootstrap.min.js";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
components: {},
|
components: {},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
.embedded-messaging > .embeddedMessagingFrame[class~="isMaximized"] {
|
||||||
|
z-index: 9999 !important;
|
||||||
|
}
|
||||||
.dockableContainer {
|
.dockableContainer {
|
||||||
&.showDockableContainer {
|
&.showDockableContainer {
|
||||||
color: $gray-600;
|
color: $gray-600;
|
||||||
|
|
|
||||||
|
|
@ -59,14 +59,6 @@ describe("sierraWebchat.vue", () => {
|
||||||
expect(window.sierraChat.closeChatModal).toHaveBeenCalled();
|
expect(window.sierraChat.closeChatModal).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("dispatches sierra-chat-closed on handleSierraOnClose", () => {
|
|
||||||
const eventSpy = jest.spyOn(window, "dispatchEvent");
|
|
||||||
wrapper.vm.createSierraConfig().onClose();
|
|
||||||
expect(eventSpy).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({ type: "sierra-chat-closed" })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("dispatches sierra-chat-transfer on handleSierraOnTransfer", () => {
|
it("dispatches sierra-chat-transfer on handleSierraOnTransfer", () => {
|
||||||
const eventSpy = jest.spyOn(window, "dispatchEvent");
|
const eventSpy = jest.spyOn(window, "dispatchEvent");
|
||||||
const transfer = {
|
const transfer = {
|
||||||
|
|
@ -77,25 +69,4 @@ describe("sierraWebchat.vue", () => {
|
||||||
expect.objectContaining({ type: "sierra-chat-transfer" })
|
expect.objectContaining({ type: "sierra-chat-transfer" })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles sierra-chat-transfer event and starts Salesforce chat with prepopulated fields", () => {
|
|
||||||
const transfer = {
|
|
||||||
data: {
|
|
||||||
first_name: "A",
|
|
||||||
last_name: "B",
|
|
||||||
email: "a@b.com",
|
|
||||||
chat_summary: "summary",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const event = new CustomEvent("sierra-chat-transfer", { detail: transfer });
|
|
||||||
window.dispatchEvent(event);
|
|
||||||
expect(window.sierraChat.closeChatModal).toHaveBeenCalled();
|
|
||||||
expect(window.embedded_svc.settings.prepopulatedPrechatFields).toEqual({
|
|
||||||
FirstName: "A",
|
|
||||||
LastName: "B",
|
|
||||||
Email: "a@b.com",
|
|
||||||
Subject: "summary",
|
|
||||||
});
|
|
||||||
expect(window.embedded_svc.liveAgentAPI.startChat).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,20 @@ import analyticsMixin from "@/mixins/analytics-mixin";
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
import { routeData } from "@/router/constants/routes";
|
import { routeData } from "@/router/constants/routes";
|
||||||
import { deepClone } from "@/helpers/object-helper";
|
import { deepClone } from "@/helpers/object-helper";
|
||||||
|
import { webchatHelper } from "@/helpers/webchat-helper";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "sierraWebchat",
|
name: "sierraWebchat",
|
||||||
mixins: [analyticsMixin],
|
mixins: [analyticsMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
transferInProgress: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const { webchatGlobalNonpersistedState, launchWebchat } = webchatHelper();
|
||||||
|
return { webchatGlobalNonpersistedState, launchWebchat };
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openSierraChatModal() {
|
openSierraChatModal() {
|
||||||
const sierra = window.sierraChat || window.sierra || window.SierraChat;
|
const sierra = window.sierraChat || window.sierra || window.SierraChat;
|
||||||
|
|
@ -35,11 +45,18 @@ export default {
|
||||||
display: "corner",
|
display: "corner",
|
||||||
onLoad: () => this.openSierraChatModal(),
|
onLoad: () => this.openSierraChatModal(),
|
||||||
onOpen: () => {},
|
onOpen: () => {},
|
||||||
onClose: () => window.dispatchEvent(new CustomEvent("sierra-chat-closed")),
|
onClose: () => {
|
||||||
onTransfer: (transfer) =>
|
if (!this.transferInProgress) {
|
||||||
|
this.webchatGlobalNonpersistedState.showWebchatButton = true;
|
||||||
|
this.transferInProgress = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTransfer: (transfer) => {
|
||||||
|
this.transferInProgress = true;
|
||||||
window.dispatchEvent(
|
window.dispatchEvent(
|
||||||
new CustomEvent("sierra-chat-transfer", { detail: transfer })
|
new CustomEvent("sierra-chat-transfer", { detail: transfer })
|
||||||
),
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getSierraContext() {
|
getSierraContext() {
|
||||||
|
|
@ -138,10 +155,7 @@ export default {
|
||||||
}
|
}
|
||||||
// Pass data to Salesforce chat if available
|
// Pass data to Salesforce chat if available
|
||||||
const transfer = event.detail;
|
const transfer = event.detail;
|
||||||
if (
|
if (window.embeddedservice_bootstrap && window.embeddedservice_bootstrap.prechatAPI) {
|
||||||
window.embedded_svc &&
|
|
||||||
typeof window.embedded_svc.bootstrapEmbeddedService === "function"
|
|
||||||
) {
|
|
||||||
if (
|
if (
|
||||||
transfer &&
|
transfer &&
|
||||||
transfer.data &&
|
transfer.data &&
|
||||||
|
|
@ -149,51 +163,22 @@ export default {
|
||||||
transfer.data.last_name &&
|
transfer.data.last_name &&
|
||||||
transfer.data.email
|
transfer.data.email
|
||||||
) {
|
) {
|
||||||
window.embedded_svc.settings.prepopulatedPrechatFields = {
|
window.embeddedservice_bootstrap.prechatAPI.setHiddenPrechatFields({
|
||||||
|
ScarlettAISummary: transfer.data.chat_summary,
|
||||||
FirstName: transfer.data.first_name,
|
FirstName: transfer.data.first_name,
|
||||||
LastName: transfer.data.last_name,
|
LastName: transfer.data.last_name,
|
||||||
Email: transfer.data.email,
|
Email: transfer.data.email,
|
||||||
Subject: transfer.data.chat_summary,
|
});
|
||||||
};
|
|
||||||
window.embedded_svc.settings.extraPrechatFormDetails = [
|
|
||||||
{
|
|
||||||
label: "Scarlett AI Summary",
|
|
||||||
value: transfer.data.chat_summary,
|
|
||||||
transcriptFields: ["Scarlett_AI_Summary__c"],
|
|
||||||
displayToAgent: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
if (
|
|
||||||
window.embedded_svc.liveAgentAPI &&
|
|
||||||
typeof window.embedded_svc.liveAgentAPI.startChat === "function"
|
|
||||||
) {
|
|
||||||
window.embedded_svc.liveAgentAPI.startChat();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
window.embedded_svc.bootstrapEmbeddedService();
|
this.webchatGlobalNonpersistedState.shouldLaunchSierra = false;
|
||||||
|
this.launchWebchat();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.addEventListener("sierra-chat-transfer", this._handleSierraTransfer);
|
window.addEventListener("sierra-chat-transfer", this._handleSierraTransfer);
|
||||||
|
|
||||||
// Notify funnel-header about Sierra chat open/close state
|
|
||||||
this._sierraChatContainerObserver = new MutationObserver(() => {
|
|
||||||
const isOpen = !!document.querySelector("[data-sierra-chat-container]");
|
|
||||||
window.dispatchEvent(
|
|
||||||
new CustomEvent("sierra-chat-visibility", { detail: { open: isOpen } })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
this._sierraChatContainerObserver.observe(document.body, {
|
|
||||||
childList: true,
|
|
||||||
subtree: true,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
window.removeEventListener("launch-sierra-webchat", this.triggerSierraChat);
|
window.removeEventListener("launch-sierra-webchat", this.triggerSierraChat);
|
||||||
window.removeEventListener("sierra-chat-transfer", this._handleSierraTransfer);
|
window.removeEventListener("sierra-chat-transfer", this._handleSierraTransfer);
|
||||||
if (this._sierraChatContainerObserver) {
|
|
||||||
this._sierraChatContainerObserver.disconnect();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,6 @@ export default {
|
||||||
return {
|
return {
|
||||||
globalAlertMessages: [],
|
globalAlertMessages: [],
|
||||||
sierraChatOpen: false,
|
sierraChatOpen: false,
|
||||||
salesforceChatOpen: false,
|
|
||||||
isSalesforceTransferInProgress: false,
|
isSalesforceTransferInProgress: false,
|
||||||
isScrolled: false,
|
isScrolled: false,
|
||||||
};
|
};
|
||||||
|
|
@ -80,20 +79,7 @@ export default {
|
||||||
return this.getCmsContent(this.cmsWidgetName, "LogoImage");
|
return this.getCmsContent(this.cmsWidgetName, "LogoImage");
|
||||||
},
|
},
|
||||||
shouldShowWebchatButton() {
|
shouldShowWebchatButton() {
|
||||||
// Hide the chat icon if Sierra chat is open
|
return this.webchatGlobalNonpersistedState.showWebchatButton;
|
||||||
if (this.sierraChatOpen) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Hide the chat icon if Salesforce chat is open or transfer is in progress
|
|
||||||
if (this.salesforceChatOpen || this.isSalesforceTransferInProgress) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Show if either Sierra is enabled or Salesforce button is available and not hidden by page
|
|
||||||
return (
|
|
||||||
(this.webchatGlobalNonpersistedState.shouldLaunchSierra ||
|
|
||||||
this.webchatGlobalNonpersistedState.showWebchatButton) &&
|
|
||||||
!this.shouldHideWebchatButtonOnPage
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -121,10 +107,6 @@ export default {
|
||||||
webchatClicked(event) {
|
webchatClicked(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.launchWebchat();
|
this.launchWebchat();
|
||||||
// Only set sierraChatOpen if Sierra experiment is active
|
|
||||||
if (this.webchatGlobalNonpersistedState.shouldLaunchSierra) {
|
|
||||||
this.sierraChatOpen = true;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
syncSierraExperimentFlag() {
|
syncSierraExperimentFlag() {
|
||||||
const experiments = store.getters.applicationUser.experiments;
|
const experiments = store.getters.applicationUser.experiments;
|
||||||
|
|
@ -164,37 +146,6 @@ export default {
|
||||||
unknownAlertEvent.displayAlert = true;
|
unknownAlertEvent.displayAlert = true;
|
||||||
this.globalAlertMessages.push(unknownAlertEvent);
|
this.globalAlertMessages.push(unknownAlertEvent);
|
||||||
}
|
}
|
||||||
// Listen for Sierra chat open/close events from sierra-webchat
|
|
||||||
this._handleSierraChatVisibility = (event) => {
|
|
||||||
this.sierraChatOpen = !!(event.detail && event.detail.open);
|
|
||||||
if (this.sierraChatOpen) {
|
|
||||||
this.isSalesforceTransferInProgress = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
window.addEventListener("sierra-chat-visibility", this._handleSierraChatVisibility);
|
|
||||||
|
|
||||||
// Listen for Salesforce chat open/close events from salesforce-webchat
|
|
||||||
this._handleSalesforceChatVisibility = (event) => {
|
|
||||||
this.salesforceChatOpen = !!(event.detail && event.detail.open === true);
|
|
||||||
};
|
|
||||||
window.addEventListener("salesforce-chat-visibility", this._handleSalesforceChatVisibility);
|
|
||||||
|
|
||||||
// Listen for transfer event to set transfer-in-progress flag
|
|
||||||
this._handleSierraToSalesforceTransfer = () => {
|
|
||||||
this.isSalesforceTransferInProgress = true;
|
|
||||||
};
|
|
||||||
window.addEventListener("sierra-chat-transfer", this._handleSierraToSalesforceTransfer);
|
|
||||||
|
|
||||||
// Reset transfer flag when Salesforce chat opens
|
|
||||||
this._handleSalesforceChatOpened = (event) => {
|
|
||||||
if (event.detail && event.detail.open === true) {
|
|
||||||
this.isSalesforceTransferInProgress = false;
|
|
||||||
this.salesforceChatOpen = true;
|
|
||||||
} else {
|
|
||||||
this.salesforceChatOpen = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
window.addEventListener("salesforce-chat-visibility", this._handleSalesforceChatOpened);
|
|
||||||
|
|
||||||
// Add scroll listener for header shadow effect
|
// Add scroll listener for header shadow effect
|
||||||
this._handleScroll = () => {
|
this._handleScroll = () => {
|
||||||
|
|
@ -203,14 +154,7 @@ export default {
|
||||||
window.addEventListener("scroll", this._handleScroll);
|
window.addEventListener("scroll", this._handleScroll);
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
window.removeEventListener("sierra-chat-closed", this._handleSierraChatClosed);
|
|
||||||
window.removeEventListener("sierra-chat-visibility", this._handleSierraChatVisibility);
|
|
||||||
window.removeEventListener(
|
|
||||||
"salesforce-chat-visibility",
|
|
||||||
this._handleSalesforceChatVisibility
|
|
||||||
);
|
|
||||||
window.removeEventListener("sierra-chat-transfer", this._handleSierraToSalesforceTransfer);
|
window.removeEventListener("sierra-chat-transfer", this._handleSierraToSalesforceTransfer);
|
||||||
window.removeEventListener("salesforce-chat-visibility", this._handleSalesforceChatOpened);
|
|
||||||
window.removeEventListener("scroll", this._handleScroll);
|
window.removeEventListener("scroll", this._handleScroll);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -57,10 +57,6 @@ export default {
|
||||||
modalWidgetName: String,
|
modalWidgetName: String,
|
||||||
pageName: String,
|
pageName: String,
|
||||||
},
|
},
|
||||||
unmounted() {
|
|
||||||
// remove any modal effects before leaving page (e.g. user hits browser back button)
|
|
||||||
this.modal?.closeModal();
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
buttonText() {
|
buttonText() {
|
||||||
return this.getCmsContent(this.modalWidgetName, "SubheaderText");
|
return this.getCmsContent(this.modalWidgetName, "SubheaderText");
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ defineRule("email-address-required", required(errorMessages.EMAIL_ADDRESS_REQUIR
|
||||||
defineRule(
|
defineRule(
|
||||||
"email-address-format",
|
"email-address-format",
|
||||||
regex(
|
regex(
|
||||||
/^([a-zA-Z0-9_\-.+]+)@([a-zA-Z0-9-]+)\.([a-zA-Z]{2,})$/,
|
/^([a-zA-Z0-9_.+]+)@([a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*\.)+([a-zA-Z]{2,})$/,
|
||||||
errorMessages.EMAIL_ADDRESS_FORMAT
|
errorMessages.EMAIL_ADDRESS_FORMAT
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -54,10 +54,6 @@ export default {
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.showSaveProgressPopup) this.modal.openModal();
|
if (this.showSaveProgressPopup) this.modal.openModal();
|
||||||
},
|
},
|
||||||
unmounted() {
|
|
||||||
// remove any modal effects before leaving page (e.g. user hits browser back button)
|
|
||||||
this.modal.closeModal();
|
|
||||||
},
|
|
||||||
emits: ["close-save-progress-popup", "save-progress"], // <--- should remove oodles of warnings in dev tools
|
emits: ["close-save-progress-popup", "save-progress"], // <--- should remove oodles of warnings in dev tools
|
||||||
props: {
|
props: {
|
||||||
modelValue: Object,
|
modelValue: Object,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import globalMethods from "@/global-methods";
|
||||||
import { endpoints } from "@/constants/endpoints";
|
import { endpoints } from "@/constants/endpoints";
|
||||||
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
||||||
import { paymentMethods } from "@/constants/payment-method-constants";
|
import { paymentMethods } from "@/constants/payment-method-constants";
|
||||||
|
import { applicationConfig } from "../constants/application-config";
|
||||||
|
|
||||||
export async function getNewSession(sessionConfig) {}
|
export async function getNewSession(sessionConfig) {}
|
||||||
|
|
||||||
|
|
@ -42,8 +43,8 @@ export async function createAdyenCheckout({
|
||||||
id: id,
|
id: id,
|
||||||
sessionData: sessionData,
|
sessionData: sessionData,
|
||||||
},
|
},
|
||||||
clientKey: "test_WYGT4F5ZT5BRRL5MPWHK6QLYOIXZXROD",
|
clientKey: applicationConfig.ADYEN_CLIENT_KEY,
|
||||||
environment: "test",
|
environment: applicationConfig.ADYEN_ENVIRONMENT,
|
||||||
locale: "en_US",
|
locale: "en_US",
|
||||||
countryCode: "US",
|
countryCode: "US",
|
||||||
showPayButton: showPayButton,
|
showPayButton: showPayButton,
|
||||||
|
|
@ -83,6 +84,21 @@ export function mapAdyenToFmgPaymentMethod(adyenMethod) {
|
||||||
return match ?? paymentMethods.CREDIT_CARD;
|
return match ?? paymentMethods.CREDIT_CARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mapFmgToAdyenPaymentMethod(fmgMethod) {
|
||||||
|
const paymentMethodMap = {
|
||||||
|
[paymentMethods.CREDIT_CARD]: "scheme",
|
||||||
|
[paymentMethods.AFTERPAY]: "afterpaytouch",
|
||||||
|
[paymentMethods.PAYPAL]: "paypal",
|
||||||
|
[paymentMethods.APPLE_PAY]: "applepay",
|
||||||
|
[paymentMethods.PAY_NOW]: null,
|
||||||
|
[paymentMethods.LATER]: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const match = paymentMethodMap[fmgMethod];
|
||||||
|
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
export function generateCcToken(adyenSessionInfo) {
|
export function generateCcToken(adyenSessionInfo) {
|
||||||
const order = store.getters.order;
|
const order = store.getters.order;
|
||||||
const locationInfo = getLocationInfo(order);
|
const locationInfo = getLocationInfo(order);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,14 @@ import { partTypeStrings } from "@/constants/part-type-strings";
|
||||||
|
|
||||||
const recalPartTypes = [partTypeStrings.RECALIBRATION, partTypeStrings.ADAS_RECALIBRATION];
|
const recalPartTypes = [partTypeStrings.RECALIBRATION, partTypeStrings.ADAS_RECALIBRATION];
|
||||||
|
|
||||||
|
export function anyPartWithRequiresRecalFlag(lineItems) {
|
||||||
|
if (!lineItems) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lineItems.glassParts?.some((li) => li.requiresRecalibration === true);
|
||||||
|
}
|
||||||
|
|
||||||
export function isRecalPartOrHasChildRecalPart(lineItem) {
|
export function isRecalPartOrHasChildRecalPart(lineItem) {
|
||||||
if (lineItem.childParts && lineItem.childParts.length > 0) {
|
if (lineItem.childParts && lineItem.childParts.length > 0) {
|
||||||
return isRecalPart(lineItem) || containsRecalParts(lineItem.childParts);
|
return isRecalPart(lineItem) || containsRecalParts(lineItem.childParts);
|
||||||
|
|
|
||||||
|
|
@ -254,6 +254,19 @@ export function getDiscountedPackageName(discountPackage) {
|
||||||
return package_names[discountPackage] || null;
|
return package_names[discountPackage] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPackageLabel(packageName) {
|
||||||
|
switch (packageName) {
|
||||||
|
case packageNames.TIER_ONE:
|
||||||
|
return externalParamPackageLabels.GLASS_ONLY;
|
||||||
|
case packageNames.TIER_TWO:
|
||||||
|
return externalParamPackageLabels.STANDARD;
|
||||||
|
case packageNames.TIER_THREE:
|
||||||
|
return externalParamPackageLabels.PREMIUM;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function maxTier(tierA, tierB) {
|
function maxTier(tierA, tierB) {
|
||||||
if (tierA === packageNames.TIER_THREE || tierB === packageNames.TIER_THREE) {
|
if (tierA === packageNames.TIER_THREE || tierB === packageNames.TIER_THREE) {
|
||||||
return packageNames.TIER_THREE;
|
return packageNames.TIER_THREE;
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,11 @@ const webchatGlobalNonpersistedState = reactive({
|
||||||
|
|
||||||
export const webchatHelper = () => {
|
export const webchatHelper = () => {
|
||||||
function launchWebchat() {
|
function launchWebchat() {
|
||||||
|
webchatGlobalNonpersistedState.showWebchatButton = false;
|
||||||
if (webchatGlobalNonpersistedState.shouldLaunchSierra) {
|
if (webchatGlobalNonpersistedState.shouldLaunchSierra) {
|
||||||
window.dispatchEvent(new CustomEvent("launch-sierra-webchat")); // launches the sierra chat
|
window.dispatchEvent(new CustomEvent("launch-sierra-webchat")); // launches the sierra chat
|
||||||
} else {
|
} else {
|
||||||
window.embedded_svc.bootstrapEmbeddedService(); // launches the salesForce prechat form
|
window.embeddedservice_bootstrap.utilAPI.launchChat(); // launches the salesForce prechat form
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ defineRule("email-address-required", required(errorMessages.EMAIL_ADDRESS_REQUIR
|
||||||
defineRule(
|
defineRule(
|
||||||
"email-address-format",
|
"email-address-format",
|
||||||
regex(
|
regex(
|
||||||
/^([a-zA-Z0-9_\-.+]+)@([a-zA-Z0-9-]+)\.([a-zA-Z]{2,})$/,
|
/^([a-zA-Z0-9_.+]+)@([a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*\.)+([a-zA-Z]{2,})$/,
|
||||||
errorMessages.EMAIL_ADDRESS_FORMAT
|
errorMessages.EMAIL_ADDRESS_FORMAT
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ defineRule("email-address-required", required(errorMessages.EMAIL_ADDRESS_REQUIR
|
||||||
defineRule(
|
defineRule(
|
||||||
"email-address-format",
|
"email-address-format",
|
||||||
regex(
|
regex(
|
||||||
/^([a-zA-Z0-9_\-.+]+)@([a-zA-Z0-9-]+)\.([a-zA-Z]{2,})$/,
|
/^([a-zA-Z0-9_.+]+)@([a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*\.)+([a-zA-Z]{2,})$/,
|
||||||
errorMessages.EMAIL_ADDRESS_FORMAT
|
errorMessages.EMAIL_ADDRESS_FORMAT
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,8 @@ export default {
|
||||||
// await any pending save-sessions. if you don't, we will save the new parent account into vuex and then a pending async save-session response overwrites it
|
// await any pending save-sessions. if you don't, we will save the new parent account into vuex and then a pending async save-session response overwrites it
|
||||||
await flushSaveSessionQueue();
|
await flushSaveSessionQueue();
|
||||||
|
|
||||||
|
await this.removeQPDLineItemIfExists();
|
||||||
|
|
||||||
await this.dispatchStoreAction(
|
await this.dispatchStoreAction(
|
||||||
this.storeActions.SAVE_PARENT_ACCOUNT_NUMBER,
|
this.storeActions.SAVE_PARENT_ACCOUNT_NUMBER,
|
||||||
this.parentAccountNumber.toString(),
|
this.parentAccountNumber.toString(),
|
||||||
|
|
@ -193,6 +195,17 @@ export default {
|
||||||
handlePageShow() {
|
handlePageShow() {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
},
|
},
|
||||||
|
async removeQPDLineItemIfExists() {
|
||||||
|
const lineItems = deepClone(store.getters.order.lineItems);
|
||||||
|
lineItems.supportingItems = baseMixin.methods.filterOutQuotePageDiscountPart(
|
||||||
|
lineItems.supportingItems
|
||||||
|
);
|
||||||
|
await baseMixin.methods.dispatchStoreAction(
|
||||||
|
storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
|
||||||
|
lineItems.supportingItems,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
window.removeEventListener("pageshow", this.handlePageShow);
|
window.removeEventListener("pageshow", this.handlePageShow);
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,11 @@
|
||||||
cmsWidgetName="PleaseProvideAddressWidget"
|
cmsWidgetName="PleaseProvideAddressWidget"
|
||||||
class="provide-address-header" />
|
class="provide-address-header" />
|
||||||
<textBlock
|
<textBlock
|
||||||
|
v-if="isMSRFeeApplicable()"
|
||||||
|
cmsWidgetName="VehicleKeysMSRMessageWidget"
|
||||||
|
class="keys-message" />
|
||||||
|
<textBlock
|
||||||
|
v-else
|
||||||
cmsWidgetName="VehicleKeysMessageWidget"
|
cmsWidgetName="VehicleKeysMessageWidget"
|
||||||
class="keys-message" />
|
class="keys-message" />
|
||||||
<mobileAddressQuestions
|
<mobileAddressQuestions
|
||||||
|
|
@ -143,6 +148,9 @@ export default {
|
||||||
getIsVehicleProtectedFromStore() {
|
getIsVehicleProtectedFromStore() {
|
||||||
return store.getters.order.serviceLocation.isVehicleProtected;
|
return store.getters.order.serviceLocation.isVehicleProtected;
|
||||||
},
|
},
|
||||||
|
isMSRFeeApplicable() {
|
||||||
|
return store.getters.order.isMSRFeeApplicable;
|
||||||
|
},
|
||||||
async forwardButtonAction() {
|
async forwardButtonAction() {
|
||||||
await this.dispatchStoreAction(
|
await this.dispatchStoreAction(
|
||||||
this.storeActions.SAVE_MOBILE_DETAILS,
|
this.storeActions.SAVE_MOBILE_DETAILS,
|
||||||
|
|
|
||||||
|
|
@ -306,6 +306,12 @@ describe("partQuestions.vue...", () => {
|
||||||
//Assert
|
//Assert
|
||||||
expect(spy).toHaveBeenNthCalledWith(
|
expect(spy).toHaveBeenNthCalledWith(
|
||||||
1,
|
1,
|
||||||
|
storeActions.SAVE_IS_OEM_GLASS_SELECTED,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect(spy).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
"savePartQuestionAnswers",
|
"savePartQuestionAnswers",
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,8 @@ export default {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.dispatchStoreAction(this.storeActions.SAVE_IS_OEM_GLASS_SELECTED, false, false);
|
||||||
|
|
||||||
// save to vuex store as order.damage.partQuestionAnswers (array)
|
// save to vuex store as order.damage.partQuestionAnswers (array)
|
||||||
// used in GET_PARTS call following this one
|
// used in GET_PARTS call following this one
|
||||||
await this.dispatchStoreAction(
|
await this.dispatchStoreAction(
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,48 @@
|
||||||
<template>
|
<template>
|
||||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
|
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
|
||||||
<div class="container-fluid">
|
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||||
<div class="row justify-content-center">
|
<div class="container">
|
||||||
<div class="col-md-6">
|
<div class="row">
|
||||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
<div class="col-12">
|
||||||
</div>
|
<div>
|
||||||
</div>
|
<alert
|
||||||
<div class="row justify-content-center">
|
ref="piaErrorAlert"
|
||||||
<div class="col-md-6 col-xl-4">
|
class="my-4"
|
||||||
<funnelSubHeader class="pb-4" cmsWidgetName="FunnelSubHeaderWidget" />
|
v-if="hasPaymentFailureError"
|
||||||
<!--
|
cmsWidgetName="PIAErrorAlertWidget"
|
||||||
... Page code goes here.
|
alertClass="alert-danger"
|
||||||
-->
|
@textLinkClicked="paymentFailedPayLater"
|
||||||
<div id="adyen-container"></div>
|
v-bind:isDismissible="false" />
|
||||||
|
</div>
|
||||||
|
<div id="card-container">
|
||||||
|
<div id="payment-container">
|
||||||
|
<funnelSubHeader class="py-5" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||||
|
<div id="adyen-container"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="cart-container">
|
||||||
|
<cart
|
||||||
|
ref="cart"
|
||||||
|
:damage="damageInfo"
|
||||||
|
:availableVaps="[]"
|
||||||
|
:allowItemRemoval="false"
|
||||||
|
v-model="lineItems"
|
||||||
|
servicePackageOptionsCmsName="ServicePackageTitle"
|
||||||
|
recyclingModalCmsWidgetName="RecycleModal"
|
||||||
|
:isInsurance="isInsurance"
|
||||||
|
:insuranceDeductible="currentDeductible"
|
||||||
|
:insuranceCompanyName="insuranceCompanyName"
|
||||||
|
:showInsuranceCoverageAs="showInsuranceCoverageAs"
|
||||||
|
:isItac="isItac"
|
||||||
|
:isNoComp="isNoComp"
|
||||||
|
:isCollapsible="false" />
|
||||||
|
<textBlock
|
||||||
|
class="payment-disclaimer"
|
||||||
|
marginTopSizeOverride="0"
|
||||||
|
cmsWidgetName="PaymentDisclaimerWidget" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<navbar
|
<navbar
|
||||||
cmsWidgetName="FunnelFooterWidget"
|
cmsWidgetName="FunnelFooterWidget"
|
||||||
ref="navbar"
|
ref="navbar"
|
||||||
|
|
@ -29,6 +59,8 @@ import Form from "vee-validate";
|
||||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||||
|
import alert from "@/ux-components/alert/alert";
|
||||||
|
import textBlock from "@/digital-components/text-block/text-block";
|
||||||
|
|
||||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
|
|
@ -42,9 +74,16 @@ import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js"
|
||||||
import { createAdyenCheckout } from "@/helpers/adyen-helper";
|
import { createAdyenCheckout } from "@/helpers/adyen-helper";
|
||||||
import { Dropin } from "@adyen/adyen-web/auto";
|
import { Dropin } from "@adyen/adyen-web/auto";
|
||||||
import { applicationConfig } from "@/constants/application-config";
|
import { applicationConfig } from "@/constants/application-config";
|
||||||
|
import { paymentMethods } from "@/constants/payment-method-constants";
|
||||||
|
|
||||||
import "@adyen/adyen-web/styles/adyen.css";
|
import "@adyen/adyen-web/styles/adyen.css";
|
||||||
import { mapAdyenToFmgPaymentMethod } from "../../helpers/adyen-helper";
|
import { mapAdyenToFmgPaymentMethod, mapFmgToAdyenPaymentMethod } from "../../helpers/adyen-helper";
|
||||||
|
import { showFmgLoadingModal } from "../../helpers/loading-modal-helper";
|
||||||
|
import cart from "@/fmg-components/cart/cart";
|
||||||
|
import { coverageStatus } from "@/constants/insurance";
|
||||||
|
import { deepClone } from "@/helpers/object-helper";
|
||||||
|
import store from "@/store";
|
||||||
|
import { debugLog } from "@/helpers/debug-log-helper.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "payment-adyen",
|
name: "payment-adyen",
|
||||||
|
|
@ -52,6 +91,8 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
sessionId: String,
|
sessionId: String,
|
||||||
|
dropinComponent: null,
|
||||||
|
hasPaymentFailureError: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -69,9 +110,15 @@ export default {
|
||||||
|
|
||||||
const resultMap = await settleAllPromises(promiseResultMap);
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
|
|
||||||
|
const hasExistingPiaError = to.query?.piaFailure;
|
||||||
|
|
||||||
// Hydrate page with results
|
// Hydrate page with results
|
||||||
next(async (vm) => {
|
next(async (vm) => {
|
||||||
vm.setCmsContent(resultMap.cmsContent);
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
|
|
||||||
|
if (hasExistingPiaError) {
|
||||||
|
vm.hasPaymentFailureError = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -89,18 +136,117 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
arePagePrerequisitesValid() {
|
arePagePrerequisitesValid() {
|
||||||
// Stub, for checking if application is capable of entering this page
|
// Service Location
|
||||||
// And/or, is all information required for this page present?
|
const serviceLocation = store.getters.order.serviceLocation;
|
||||||
// To prevent sequence-breaking from the end-user.
|
const mobileReqs = !!(
|
||||||
|
serviceLocation.address &&
|
||||||
|
serviceLocation.city &&
|
||||||
|
serviceLocation.state &&
|
||||||
|
serviceLocation.zipCode
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
const providerLocation = serviceLocation.provider.address;
|
||||||
|
const dropOffInshopReqs = !!(
|
||||||
|
providerLocation.streetAddress &&
|
||||||
|
providerLocation.city &&
|
||||||
|
providerLocation.state &&
|
||||||
|
providerLocation.zipCode
|
||||||
|
);
|
||||||
|
|
||||||
|
const isMobile = serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE;
|
||||||
|
|
||||||
|
const serviceLocationReqs =
|
||||||
|
(isMobile && mobileReqs) || (!isMobile && dropOffInshopReqs);
|
||||||
|
|
||||||
|
// Insurance
|
||||||
|
const isInsuranceSet = store.getters.order.payment.isInsurance !== null;
|
||||||
|
|
||||||
|
// Schedule
|
||||||
|
const schedule = store.getters.order.schedule;
|
||||||
|
const scheduleReqs = !!(
|
||||||
|
schedule.date &&
|
||||||
|
schedule.startTime &&
|
||||||
|
schedule.endTime &&
|
||||||
|
schedule.jobMaxMinutes &&
|
||||||
|
schedule.jobMinMinutes
|
||||||
|
);
|
||||||
|
|
||||||
|
// Customer
|
||||||
|
const customer = store.getters.order.customer;
|
||||||
|
const customerReqs = !!(
|
||||||
|
customer.firstName &&
|
||||||
|
customer.lastName &&
|
||||||
|
customer.phoneNumber &&
|
||||||
|
customer.emailAddress
|
||||||
|
);
|
||||||
|
|
||||||
|
const paymentMethodReqs =
|
||||||
|
store.getters.order.payment.isPia !== null &&
|
||||||
|
(store.getters.order.payment.isPia || !!store.getters.order.payment.piaType);
|
||||||
|
|
||||||
|
const preReqResult =
|
||||||
|
serviceLocationReqs &&
|
||||||
|
isInsuranceSet &&
|
||||||
|
scheduleReqs &&
|
||||||
|
customerReqs &&
|
||||||
|
paymentMethodReqs;
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
{
|
||||||
|
debugLog("--- payment-adyen.vue pagePrereqs start ---", null, !preReqResult);
|
||||||
|
debugLog("mobileReqs:", mobileReqs, !preReqResult);
|
||||||
|
debugLog("serviceLocation.address:", serviceLocation.address, !preReqResult);
|
||||||
|
debugLog("serviceLocation.city:", serviceLocation.city, !preReqResult);
|
||||||
|
debugLog("serviceLocation.state:", serviceLocation.state, !preReqResult);
|
||||||
|
debugLog("serviceLocation.zipCode:", serviceLocation.zipCode, !preReqResult);
|
||||||
|
|
||||||
|
debugLog("", null, !preReqResult);
|
||||||
|
|
||||||
|
debugLog("dropOffInshopReqs:", dropOffInshopReqs, !preReqResult);
|
||||||
|
debugLog("serviceLocationReqs:", serviceLocationReqs, !preReqResult);
|
||||||
|
debugLog("providerLocation.streetAddress:", providerLocation.streetAddress, !preReqResult);
|
||||||
|
debugLog("providerLocation.city:", providerLocation.city, !preReqResult);
|
||||||
|
debugLog("providerLocation.state:", providerLocation.state, !preReqResult);
|
||||||
|
debugLog("providerLocation.zipCode:", providerLocation.zipCode, !preReqResult);
|
||||||
|
|
||||||
|
debugLog("", null, !preReqResult);
|
||||||
|
|
||||||
|
debugLog("isInsuranceSet:", isInsuranceSet, !preReqResult);
|
||||||
|
|
||||||
|
debugLog("", null, !preReqResult);
|
||||||
|
|
||||||
|
debugLog("scheduleReqs:", scheduleReqs, !preReqResult);
|
||||||
|
debugLog("schedule.date:", schedule.date, !preReqResult);
|
||||||
|
debugLog("schedule.startTime:", schedule.startTime, !preReqResult);
|
||||||
|
debugLog("schedule.endTime:", schedule.endTime, !preReqResult);
|
||||||
|
debugLog("schedule.jobMaxMinutes:", schedule.jobMaxMinutes, !preReqResult);
|
||||||
|
debugLog("schedule.jobMinMinutes:", schedule.jobMinMinutes, !preReqResult);
|
||||||
|
|
||||||
|
debugLog("", null, !preReqResult);
|
||||||
|
|
||||||
|
debugLog("customerReqs:", customerReqs, !preReqResult);
|
||||||
|
debugLog("customer.firstName:", customer.firstName, !preReqResult);
|
||||||
|
debugLog("customer.lastName:", customer.lastName, !preReqResult);
|
||||||
|
debugLog("customer.phoneNumber:", customer.phoneNumber, !preReqResult);
|
||||||
|
debugLog("customer.emailAddress:", customer.emailAddress, !preReqResult);
|
||||||
|
|
||||||
|
debugLog("", null, !preReqResult);
|
||||||
|
|
||||||
|
debugLog("paymentMethodReqs:", paymentMethodReqs, !preReqResult);
|
||||||
|
debugLog("store.getters.order.payment.isPia:", store.getters.order?.payment?.isPia, !preReqResult);
|
||||||
|
debugLog("store.getters.order.payment.piaType:", store.getters.order?.payment?.piaType, !preReqResult);
|
||||||
|
|
||||||
|
debugLog("--- payment-adyen.vue pagePrereqs end ---", null, !preReqResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return preReqResult;
|
||||||
},
|
},
|
||||||
|
|
||||||
async initializeAdyen() {
|
async initializeAdyen() {
|
||||||
console.log(`Price = ${this.amountDue}`);
|
console.log(`Price = ${this.amountDue}`);
|
||||||
console.log(`Adyen Price = ${this.adyenPriceTotal}`);
|
console.log(`Adyen Price = ${this.adyenPriceTotal}`);
|
||||||
|
|
||||||
const requestBody = this.adyenInitRequestInfo;
|
const requestBody = this.getAdyenInitRequestInfo();
|
||||||
|
|
||||||
console.log(`Calling with:`);
|
console.log(`Calling with:`);
|
||||||
console.log(requestBody);
|
console.log(requestBody);
|
||||||
|
|
@ -148,8 +294,16 @@ export default {
|
||||||
|
|
||||||
console.log(checkout);
|
console.log(checkout);
|
||||||
|
|
||||||
window.checkout = checkout;
|
const expiryTime = new Date(checkout.options.expiresAt);
|
||||||
const dropin = new Dropin(checkout, {
|
const expiryInterval = expiryTime.getTime() - new Date().getTime();
|
||||||
|
|
||||||
|
const handleTimeout = () => {
|
||||||
|
this.resetAdyenDropin();
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeout = setTimeout(handleTimeout, expiryInterval);
|
||||||
|
|
||||||
|
const configuration = {
|
||||||
paymentMethodsConfiguration: {
|
paymentMethodsConfiguration: {
|
||||||
ideal: {
|
ideal: {
|
||||||
showImage: true,
|
showImage: true,
|
||||||
|
|
@ -159,10 +313,10 @@ export default {
|
||||||
value: this.adyenPriceTotal,
|
value: this.adyenPriceTotal,
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
},
|
},
|
||||||
environment: "test", // Change this to "live" when you're ready to accept live PayPal payments
|
environment: applicationConfig.ADYEN_ENVIRONMENT,
|
||||||
countryCode: "US", // Only needed for test. This will be automatically retrieved when you are in production.
|
countryCode: "US", // Only needed for test. This will be automatically retrieved when you are in production.
|
||||||
blockPayPalVenmoButton: false,
|
blockPayPalVenmoButton: true,
|
||||||
//blockPayPalPayLaterButton: true
|
blockPayPalPayLaterButton: true,
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
hasHolderName: true,
|
hasHolderName: true,
|
||||||
|
|
@ -171,7 +325,21 @@ export default {
|
||||||
name: "Credit or debit card",
|
name: "Credit or debit card",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}).mount("#adyen-container");
|
};
|
||||||
|
|
||||||
|
const adyenPaymentType = mapFmgToAdyenPaymentMethod(this.piaType);
|
||||||
|
|
||||||
|
if (adyenPaymentType) {
|
||||||
|
configuration.openPaymentMethod = {
|
||||||
|
type: adyenPaymentType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const dropin = new Dropin(checkout, configuration);
|
||||||
|
|
||||||
|
this.dropinComponent = dropin;
|
||||||
|
|
||||||
|
dropin.mount("#adyen-container");
|
||||||
},
|
},
|
||||||
|
|
||||||
async handleCompletedPayment(result) {
|
async handleCompletedPayment(result) {
|
||||||
|
|
@ -189,6 +357,8 @@ export default {
|
||||||
console.log(`Get session payload =`);
|
console.log(`Get session payload =`);
|
||||||
console.log(paymentSessionRequest);
|
console.log(paymentSessionRequest);
|
||||||
|
|
||||||
|
showFmgLoadingModal(true);
|
||||||
|
|
||||||
const response = await globalMethods.callHttpClient({
|
const response = await globalMethods.callHttpClient({
|
||||||
method: endpoints.GetAdyenSessionResult.method,
|
method: endpoints.GetAdyenSessionResult.method,
|
||||||
endpoint: endpoints.GetAdyenSessionResult.url,
|
endpoint: endpoints.GetAdyenSessionResult.url,
|
||||||
|
|
@ -233,7 +403,20 @@ export default {
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
await baseMixin.methods.dispatchStoreAction(storeActions.SAVE_CCTOKEN, ccToken, false);
|
if (paymentMethod === paymentMethods.PAYPAL) {
|
||||||
|
await baseMixin.methods.dispatchStoreAction(
|
||||||
|
storeActions.SAVE_PAYPAL_TOKEN,
|
||||||
|
responseJson?.storedToken,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await baseMixin.methods.dispatchStoreAction(
|
||||||
|
storeActions.SAVE_CCTOKEN,
|
||||||
|
ccToken,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await baseMixin.methods.dispatchStoreAction(
|
await baseMixin.methods.dispatchStoreAction(
|
||||||
storeActions.SAVE_NEXTGEN_SETTLED_AMOUNT,
|
storeActions.SAVE_NEXTGEN_SETTLED_AMOUNT,
|
||||||
this.amountDue,
|
this.amountDue,
|
||||||
|
|
@ -246,33 +429,33 @@ export default {
|
||||||
console.log(`Result =`);
|
console.log(`Result =`);
|
||||||
console.log(result);
|
console.log(result);
|
||||||
|
|
||||||
// Fetch session info
|
const wasPaymentCancelled = result?.resultCode === "Cancelled";
|
||||||
const paymentSessionRequest = {
|
|
||||||
zipCodeCtu: this.locationInfo.zipCodeCtu,
|
|
||||||
workOrderNumber: this.workOrderNumberLastSixDigits,
|
|
||||||
sourceSystem: this.sourceSystem,
|
|
||||||
sessionId: this.sessionId,
|
|
||||||
sessionResult: result?.sessionResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log(`Get session payload =`);
|
if (!wasPaymentCancelled) {
|
||||||
console.log(paymentSessionRequest);
|
const stringToLog = `ADYEN PAYMENT NOT AUTHORIZED. Code = ${result?.resultCode}. Id = ${this.sessionId}`;
|
||||||
|
|
||||||
try {
|
await global.$logger.logError(stringToLog);
|
||||||
const response = await globalMethods.callHttpClient({
|
|
||||||
method: endpoints.GetAdyenSessionResult.method,
|
|
||||||
endpoint: endpoints.GetAdyenSessionResult.url,
|
|
||||||
payload: paymentSessionRequest,
|
|
||||||
logApiCall: true,
|
|
||||||
pageNameToLog: "payment-adyen",
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(response);
|
this.hasPaymentFailureError = true;
|
||||||
} catch (error) {
|
|
||||||
console.log(`Error getting session info after failure.`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.dropinComponent?.update();
|
||||||
|
},
|
||||||
|
async handleError(error) {
|
||||||
|
console.log(error);
|
||||||
|
|
||||||
|
const wasPaymentCancelled = error?.name === "CANCEL";
|
||||||
|
|
||||||
|
if (!wasPaymentCancelled) {
|
||||||
|
const stringToLog = `ADYEN ERROR. Name = ${error?.name}. Details = ${error?.message}.`;
|
||||||
|
|
||||||
|
await global.$logger.logError(stringToLog);
|
||||||
|
|
||||||
|
this.hasPaymentFailureError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dropinComponent?.update();
|
||||||
},
|
},
|
||||||
handleError(error) {},
|
|
||||||
|
|
||||||
async saveAndSubmitWorkOrder() {
|
async saveAndSubmitWorkOrder() {
|
||||||
// Work order submission after successful payment.
|
// Work order submission after successful payment.
|
||||||
|
|
@ -285,15 +468,12 @@ export default {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`error: response from submit work order: ${error.message}`);
|
console.log(`error: response from submit work order: ${error.message}`);
|
||||||
|
|
||||||
// navigate to error page
|
this.$router.navigateWithoutSaving(
|
||||||
const errorPayload = {
|
this.navigationScenarios.PIA_ERROR,
|
||||||
cause: `Error while submitting work order after Adyen payment.`,
|
this.pageName
|
||||||
currentPage: this.pageName,
|
);
|
||||||
sessionId: this.sessionId,
|
|
||||||
idempotencyKey: this.idempotencyKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.$router.handleSoftError(errorPayload);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear order
|
// clear order
|
||||||
|
|
@ -303,10 +483,57 @@ export default {
|
||||||
this.pageName
|
this.pageName
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
async paymentFailedPayLater() {
|
||||||
adyenInitRequestInfo() {
|
console.log(`Doing final work order submission...`);
|
||||||
|
showFmgLoadingModal(true);
|
||||||
|
|
||||||
|
await this.dispatchStoreAction(
|
||||||
|
storeActions.SAVE_PAYMENT_METHOD_CHOICE,
|
||||||
|
paymentMethods.PAY_LATER,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await submitWorkOrder({
|
||||||
|
pageNameToLog: this.pageName,
|
||||||
|
submitAfterSave: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await baseMixin.methods.dispatchStoreAction(storeActions.CREATE_SUBMITTED_STATE);
|
||||||
|
|
||||||
|
this.$router.navigateWithoutSaving(
|
||||||
|
this.navigationScenarios.CLICKED_PAY_LATER,
|
||||||
|
this.pageName
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Final submission failed after failed payment.`);
|
||||||
|
this.$router.handleSoftError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async resetAdyenDropin() {
|
||||||
|
this?.dropinComponent?.unmount();
|
||||||
|
await this.initializeAdyen();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Now a Method so it is always freshly called and not cached.
|
||||||
|
getIdempotencyKey() {
|
||||||
|
const currentDateTime = new Date();
|
||||||
|
const currentHour = currentDateTime.getUTCHours();
|
||||||
|
const currentDate = currentDateTime.getUTCDate();
|
||||||
|
const id = this?.$store?.getters?.order?.referralCorrelationId;
|
||||||
|
const system = this.sourceSystem;
|
||||||
|
const total = this.adyenPriceTotal;
|
||||||
|
|
||||||
|
return `${id}-${system}-${currentDate}-${currentHour}-${total}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Now a Method so it is always freshly called and not cached.
|
||||||
|
getAdyenInitRequestInfo() {
|
||||||
return {
|
return {
|
||||||
sourceSystem: this.sourceSystem,
|
sourceSystem: this.sourceSystem,
|
||||||
referralSequenceNumber: this.$store.getters.order.referralSequenceNumber,
|
referralSequenceNumber: this.$store.getters.order.referralSequenceNumber,
|
||||||
|
|
@ -323,9 +550,15 @@ export default {
|
||||||
IP: "127.0.0.1", // TODO
|
IP: "127.0.0.1", // TODO
|
||||||
firstName: this.$store.getters.order.customer.firstName,
|
firstName: this.$store.getters.order.customer.firstName,
|
||||||
lastName: this.$store.getters.order.customer.lastName,
|
lastName: this.$store.getters.order.customer.lastName,
|
||||||
idempotencyKey: this.idempotencyKey,
|
idempotencyKey: this.getIdempotencyKey(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
piaType() {
|
||||||
|
return this.$store.getters.order.payment.piaType;
|
||||||
|
},
|
||||||
|
|
||||||
workOrderNumberLastSixDigits() {
|
workOrderNumberLastSixDigits() {
|
||||||
const workOrderNumber = this.$store.getters.order.workOrderNumber ?? "";
|
const workOrderNumber = this.$store.getters.order.workOrderNumber ?? "";
|
||||||
|
|
@ -382,10 +615,6 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
idempotencyKey() {
|
|
||||||
return `${this.$store.getters.order.referralCorrelationId}-${this.sourceSystem}`;
|
|
||||||
},
|
|
||||||
|
|
||||||
sourceSystem() {
|
sourceSystem() {
|
||||||
return "FMG-2.0";
|
return "FMG-2.0";
|
||||||
},
|
},
|
||||||
|
|
@ -395,7 +624,41 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
adyenPriceTotal() {
|
adyenPriceTotal() {
|
||||||
return this.amountDue * 100;
|
return Math.round(this.amountDue * 100);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Cart info
|
||||||
|
damageInfo() {
|
||||||
|
return this.$store.getters.damage;
|
||||||
|
},
|
||||||
|
isInsurance() {
|
||||||
|
return this.$store.getters.payment.isInsurance;
|
||||||
|
},
|
||||||
|
isNoComp() {
|
||||||
|
return this.$store.getters.policy.isNoComp;
|
||||||
|
},
|
||||||
|
isItac() {
|
||||||
|
return this.$store.getters.policy.isItac;
|
||||||
|
},
|
||||||
|
showInsuranceCoverageAs() {
|
||||||
|
if (!this.isInsurance) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isNoComp || this.isItac) {
|
||||||
|
return coverageStatus.NOCOMP;
|
||||||
|
} else {
|
||||||
|
return this.$store.getters.payment.insuranceCoverage.coverageStatus;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
currentDeductible() {
|
||||||
|
return this.$store.getters.policy.currentDeductible;
|
||||||
|
},
|
||||||
|
insuranceCompanyName() {
|
||||||
|
return this.$store.getters.policy.insuranceCompanyName;
|
||||||
|
},
|
||||||
|
lineItems() {
|
||||||
|
return deepClone(this.$store.getters.lineItems);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -408,6 +671,39 @@ export default {
|
||||||
funnelHeader,
|
funnelHeader,
|
||||||
funnelSubHeader,
|
funnelSubHeader,
|
||||||
navbar,
|
navbar,
|
||||||
|
cart,
|
||||||
|
alert,
|
||||||
|
textBlock,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
#card-container {
|
||||||
|
display: flex;
|
||||||
|
& > #payment-container {
|
||||||
|
flex-basis: 55%;
|
||||||
|
margin-right: 9%;
|
||||||
|
}
|
||||||
|
& > #cart-container {
|
||||||
|
flex-basis: 36%;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-down(lg) {
|
||||||
|
flex-direction: column;
|
||||||
|
& > #payment-container {
|
||||||
|
flex-basis: auto;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
& > #cart-container {
|
||||||
|
flex-basis: auto;
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-disclaimer p {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -810,11 +810,51 @@ export default {
|
||||||
const piaInsurance = this.getSettingValue(experimentSettings.PIA_INSURANCE);
|
const piaInsurance = this.getSettingValue(experimentSettings.PIA_INSURANCE);
|
||||||
|
|
||||||
if (this.isInsurance) {
|
if (this.isInsurance) {
|
||||||
return piaInsurance === "true" && this.currentDeductible > 0;
|
return (
|
||||||
|
(piaInsurance === "true" || this.isConditionalPIAEnabled) &&
|
||||||
|
this.currentDeductible > 0
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return piaExperience === "PIA Optional" || piaExperience === "PIA Required";
|
if (piaExperience?.includes("PIA Disabled")) {
|
||||||
|
return this.isConditionalPIAEnabled;
|
||||||
|
} else {
|
||||||
|
return piaExperience === "PIA Optional" || piaExperience === "PIA Required";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
isConditionalPIAEnabled() {
|
||||||
|
const conditionalProviderNumbers = this.getSettingValue(
|
||||||
|
experimentSettings.CONDITIONAL_PROVIDER_NUMBERS
|
||||||
|
);
|
||||||
|
const conditionalProviderNumbers2 = this.getSettingValue(
|
||||||
|
experimentSettings.CONDITIONAL_PROVIDER_NUMBERS_2
|
||||||
|
);
|
||||||
|
const conditionalProviderShutoffDate = this.getSettingValue(
|
||||||
|
experimentSettings.CONDITIONAL_PROVIDER_SHUTOFF_DATE
|
||||||
|
);
|
||||||
|
|
||||||
|
const allConditionalProviderNumbers = [
|
||||||
|
conditionalProviderNumbers,
|
||||||
|
conditionalProviderNumbers2,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(",");
|
||||||
|
const providerNumber =
|
||||||
|
this.$store.getters.order.serviceLocation.provider.providerNumber;
|
||||||
|
const appointmentDate = new Date(this.$store.getters.order.schedule.date);
|
||||||
|
const shutoffDate = new Date(conditionalProviderShutoffDate);
|
||||||
|
if (allConditionalProviderNumbers && conditionalProviderShutoffDate) {
|
||||||
|
const providerNumbersArray = allConditionalProviderNumbers
|
||||||
|
.split(",")
|
||||||
|
.map((num) => num.trim());
|
||||||
|
if (!providerNumbersArray.includes(providerNumber)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return appointmentDate <= shutoffDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
paymentMethod() {
|
paymentMethod() {
|
||||||
if (!this.isPiaEnabled) {
|
if (!this.isPiaEnabled) {
|
||||||
return paymentMethods.LATER;
|
return paymentMethods.LATER;
|
||||||
|
|
|
||||||
|
|
@ -745,10 +745,16 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updatePaymentMethod(paymentMethod) {
|
updatePaymentMethod(paymentMethod) {
|
||||||
// if the payment method is not set yet, then set it based on what HOP sends us
|
debugLog(
|
||||||
|
`updatePaymentMethod(${paymentMethod}) and existing store.getters.order.payment.piaType = ${store?.getters?.order?.payment?.piaType}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// if the payment method is not set yet, then set it based on what HOP sends
|
||||||
|
// or if its not already set to AfterPay, always set it based on what HOP sends
|
||||||
if (
|
if (
|
||||||
store.getters.order.payment.piaType == paymentMethods.PAY_NOW ||
|
store.getters.order.payment.piaType == paymentMethods.PAY_NOW ||
|
||||||
store.getters.order.payment.piaType == paymentMethods.NONE
|
store.getters.order.payment.piaType == paymentMethods.NONE ||
|
||||||
|
store.getters.order.payment.piaType != paymentMethods.AFTERPAY
|
||||||
) {
|
) {
|
||||||
console.log(new Date() + " UPDATE PaymentMethod from HOP:" + paymentMethod);
|
console.log(new Date() + " UPDATE PaymentMethod from HOP:" + paymentMethod);
|
||||||
this.dispatchStoreAction(
|
this.dispatchStoreAction(
|
||||||
|
|
|
||||||
70
src/layouts/policy-info/policy-info.spec.js
Normal file
70
src/layouts/policy-info/policy-info.spec.js
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
import policyInfo from "@/layouts/policy-info/policy-info.vue";
|
||||||
|
import { shallowMount } from "@vue/test-utils";
|
||||||
|
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||||
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
|
|
||||||
|
// Mock our module for promises.
|
||||||
|
jest.mock("@/helpers/layout-helper.js", () => ({
|
||||||
|
settleAllPromises: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock fetchCmsContentForPage
|
||||||
|
jest.mock("@/helpers/cms-content-helper", () => ({
|
||||||
|
fetchCmsContentForPage: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("policy-info.vue", () => {
|
||||||
|
describe("component rendering", () => {
|
||||||
|
test("renders funnelHeader component", () => {
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
expect(wrapper.findComponent({ name: "funnelHeader" }).exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders funnelSubHeader component", () => {
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
expect(wrapper.findComponent({ name: "funnelSubHeader" }).exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders navbar component", () => {
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
expect(wrapper.findComponent({ name: "navbar" }).exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders Form component", () => {
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
expect(wrapper.findComponent({ name: "Form" }).exists()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("methods", () => {
|
||||||
|
test("arePagePrerequisitesValid returns true", () => {
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
expect(wrapper.vm.arePagePrerequisitesValid()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupMocks() {
|
||||||
|
const mockCmsContent = {
|
||||||
|
FunnelHeaderWidget: { Text: "Header" },
|
||||||
|
FunnelSubHeaderWidget: { Text: "Subheader" },
|
||||||
|
FunnelFooterWidget: { Text: "Footer" },
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchCmsContentForPage.mockResolvedValue(mockCmsContent);
|
||||||
|
settleAllPromises.mockResolvedValue({ cmsContent: mockCmsContent });
|
||||||
|
|
||||||
|
const mountOptions = getMountOptions({
|
||||||
|
route: { name: "policy-info", query: {}, params: {} },
|
||||||
|
router: {
|
||||||
|
navigate: jest.fn(),
|
||||||
|
navigateWithSaving: jest.fn(),
|
||||||
|
navigateWithoutSaving: jest.fn(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapper = shallowMount(policyInfo, mountOptions);
|
||||||
|
|
||||||
|
return { wrapper };
|
||||||
|
}
|
||||||
72
src/layouts/policy-info/policy-info.vue
Normal file
72
src/layouts/policy-info/policy-info.vue
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
<template>
|
||||||
|
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||||
|
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||||
|
<funnelSubHeader class="pb-4" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||||
|
<!--
|
||||||
|
... Page code goes here.
|
||||||
|
-->
|
||||||
|
<navbar
|
||||||
|
cmsWidgetName="FunnelFooterWidget"
|
||||||
|
ref="navbar"
|
||||||
|
:isForwardActionDisabled="!meta.valid"
|
||||||
|
@back-clicked="backButtonAction"
|
||||||
|
@ForwardClicked="forwardButtonAction" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||||
|
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||||
|
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||||
|
import { Form } from "vee-validate";
|
||||||
|
|
||||||
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "policy-info",
|
||||||
|
mixins: [],
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Form,
|
||||||
|
funnelHeader,
|
||||||
|
navbar,
|
||||||
|
funnelSubHeader,
|
||||||
|
},
|
||||||
|
// Ensure CMS content is fetched during route navigation without depending on `to`/`from`
|
||||||
|
async beforeRouteEnter(to, from, next) {
|
||||||
|
const pageName = to.name;
|
||||||
|
const cmsContentPromise = fetchCmsContentForPage(pageName);
|
||||||
|
const promiseResultMap = [
|
||||||
|
{
|
||||||
|
resultKey: "cmsContent",
|
||||||
|
promise: cmsContentPromise,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
|
next((vm) => {
|
||||||
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async backButtonAction() {
|
||||||
|
// Stub, for navigating back via nav-bar.
|
||||||
|
},
|
||||||
|
async forwardButtonAction() {
|
||||||
|
// Stub, for navigating forward via nav-bar
|
||||||
|
},
|
||||||
|
arePagePrerequisitesValid() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -253,9 +253,10 @@ export default {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
h3 {
|
h3 {
|
||||||
font-family: UrbanistSemibold;
|
font-family: UrbanistBold;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
margin: 0 0 0 0.5rem;
|
margin: 0 0 0 0.5rem;
|
||||||
|
color: $black;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p {
|
p {
|
||||||
|
|
|
||||||
167
src/layouts/quote/oem-glass-question/oem-glass-question.vue
Normal file
167
src/layouts/quote/oem-glass-question/oem-glass-question.vue
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="oem-glass-question-container py-5 mt-3 mb-6"
|
||||||
|
:class="[isExpanded ? 'expanded' : '']">
|
||||||
|
<div class="oem-glass-question-header" @click="toggleIsExpanded">
|
||||||
|
{{ headerText }}
|
||||||
|
</div>
|
||||||
|
<div class="oem-glass-question-body">
|
||||||
|
<div class="oem-glass-question-copy" v-html="bodyText"></div>
|
||||||
|
<div class="oem-glass-question-option">
|
||||||
|
<checkboxQuestion
|
||||||
|
class="mt-3 ms-1"
|
||||||
|
cmsWidgetName="OemGlassInputQuestionWidget"
|
||||||
|
v-model="installOemGlass" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import checkboxQuestion from "@/digital-components/checkbox-question/checkbox-question";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "oem-part-question",
|
||||||
|
props: {
|
||||||
|
cmsWidgetName: String,
|
||||||
|
modelValue: Boolean,
|
||||||
|
oemGlassPartPrice: Number,
|
||||||
|
aftermarketGlassPartPrice: Number,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isExpanded: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
headerText() {
|
||||||
|
return this.getCmsContent(this.cmsWidgetName, "HeaderText");
|
||||||
|
},
|
||||||
|
bodyText() {
|
||||||
|
let priceDifference = this.oemGlassPartPrice - this.aftermarketGlassPartPrice;
|
||||||
|
let bodyText = this.getCmsContent(this.cmsWidgetName, "BodyText");
|
||||||
|
bodyText = bodyText.replace(
|
||||||
|
"{custom:OEMGLASSPRICE}",
|
||||||
|
"<span id='price-dif'>$" + priceDifference.toFixed(2) + "</span>"
|
||||||
|
);
|
||||||
|
return bodyText;
|
||||||
|
},
|
||||||
|
footerText() {
|
||||||
|
return this.getCmsContent(this.cmsWidgetName, "FooterText");
|
||||||
|
},
|
||||||
|
installOemGlass: {
|
||||||
|
get() {
|
||||||
|
return this.modelValue;
|
||||||
|
},
|
||||||
|
set(newValue) {
|
||||||
|
this.$emit("update:modelValue", newValue);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleIsExpanded() {
|
||||||
|
this.setIsExpanded(!this.isExpanded);
|
||||||
|
},
|
||||||
|
setIsExpanded(newVal) {
|
||||||
|
this.isExpanded = newVal;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
checkboxQuestion,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.oem-glass-question-container {
|
||||||
|
border-top: 1px solid gray;
|
||||||
|
|
||||||
|
@include media-breakpoint-up(lg) {
|
||||||
|
margin: 0 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oem-glass-question-header {
|
||||||
|
font-family: UrbanistSemibold, Arial, Helvetica, sans-serif;
|
||||||
|
color: $blue;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
span {
|
||||||
|
flex-grow: 1;
|
||||||
|
color: $black;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 9'%3E%3Cpath d='M7.99282 0.117702C7.75716 0.116049 7.53044 0.207797 7.3623 0.37286L0.255553 7.46878C0.0862976 7.63796 -0.00878906 7.86742 -0.00878906 8.10667C-0.00878906 8.34593 0.0862976 8.57539 0.255553 8.74457C0.424808 8.91374 0.654368 9.00879 0.893731 9.00879C1.13309 9.00879 1.36265 8.91374 1.53191 8.74457L7.99282 2.27378L14.4614 8.74457C14.6307 8.91205 14.8595 9.00547 15.0977 9.00428C15.3359 9.00308 15.5638 8.90737 15.7314 8.73819C15.8989 8.56901 15.9924 8.34022 15.9912 8.10216C15.99 7.8641 15.8942 7.63627 15.725 7.46878L8.6259 0.377963C8.45765 0.21088 8.22999 0.117289 7.99282 0.117702Z' fill='%230070d1'/%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center center;
|
||||||
|
color: $blue;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: inline-flex;
|
||||||
|
position: relative;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.oem-glass-question-body {
|
||||||
|
max-height: 0;
|
||||||
|
transition: all 350ms ease-in;
|
||||||
|
overflow: hidden;
|
||||||
|
visibility: hidden;
|
||||||
|
|
||||||
|
.oem-glass-question-option {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
font-family: UrbanistSemibold, Arial, Helvetica, sans-serif;
|
||||||
|
|
||||||
|
.oem-glass-question-strikethrough-price {
|
||||||
|
font-size: smaller;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oem-glass-question-free-price {
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: bolder;
|
||||||
|
border-radius: 1em;
|
||||||
|
background-color: $green-300;
|
||||||
|
padding: 0 0.5em;
|
||||||
|
margin-inline-start: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.expanded {
|
||||||
|
.oem-glass-question-header {
|
||||||
|
&::after {
|
||||||
|
transform: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.oem-glass-question-body {
|
||||||
|
visibility: visible;
|
||||||
|
max-height: none;
|
||||||
|
padding-top: 0.5em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
|
||||||
|
.oem-glass-question-copy {
|
||||||
|
#price-dif {
|
||||||
|
font-weight: $font-weight-700;
|
||||||
|
color: $black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.oem-glass-question-option {
|
||||||
|
.small {
|
||||||
|
font-weight: $font-weight-700;
|
||||||
|
color: $black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -118,6 +118,9 @@ jest.mock("@/mixins/experiment-mixin.js", () => ({
|
||||||
if (settingName === mockExperimentSettings.DISPLAY_AFTERPAY_BREAKOUT_DISPLAY) {
|
if (settingName === mockExperimentSettings.DISPLAY_AFTERPAY_BREAKOUT_DISPLAY) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (settingName === mockExperimentSettings.OFFER_OEM) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return "test";
|
return "test";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
@vapsItemsSelected="vapsItemsSelectedAction"
|
@vapsItemsSelected="vapsItemsSelectedAction"
|
||||||
@quotePageDiscountSelected="quotePageDiscountSelectedAction"
|
@quotePageDiscountSelected="quotePageDiscountSelectedAction"
|
||||||
@currentNumberOfPackages="currentNumberOfPackagesAction"
|
@currentNumberOfPackages="currentNumberOfPackagesAction"
|
||||||
|
@servicePackageSelected="servicePackageSelectedAction"
|
||||||
:servicePackage="servicePackage"
|
:servicePackage="servicePackage"
|
||||||
:activePromos="lineItems.promos"
|
:activePromos="lineItems.promos"
|
||||||
:availableQuotePageDiscounts="quoteDiscountPartsInfo"
|
:availableQuotePageDiscounts="quoteDiscountPartsInfo"
|
||||||
|
|
@ -85,6 +86,13 @@
|
||||||
isInsuranceSelected ? isInsuranceContinueButtonText : ''
|
isInsuranceSelected ? isInsuranceContinueButtonText : ''
|
||||||
" />
|
" />
|
||||||
|
|
||||||
|
<oemGlassQuestion
|
||||||
|
cmsWidgetName="OemGlassQuestionWidget"
|
||||||
|
v-if="offerOem && !isInsuranceSelected"
|
||||||
|
v-model="isOemGlassSelected"
|
||||||
|
:oemGlassPartPrice="oemGlassPartPrice"
|
||||||
|
:aftermarketGlassPartPrice="aftermarketGlassPartPrice" />
|
||||||
|
|
||||||
<saveProgressModalQuestion
|
<saveProgressModalQuestion
|
||||||
modalWidgetName="SaveProgressModalWidget"
|
modalWidgetName="SaveProgressModalWidget"
|
||||||
modalName="SaveProgressModal"
|
modalName="SaveProgressModal"
|
||||||
|
|
@ -128,6 +136,7 @@ import afterpayModalBanner from "@/layouts/quote/afterpay-modal-banner/afterpay-
|
||||||
import recalDisclaimer from "@/layouts/quote/recal-disclaimer/recal-disclaimer.vue";
|
import recalDisclaimer from "@/layouts/quote/recal-disclaimer/recal-disclaimer.vue";
|
||||||
import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question";
|
import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question";
|
||||||
import saveProgressPopupQuestion from "@/fmg-components/save-progress-popup-question/save-progress-popup-question";
|
import saveProgressPopupQuestion from "@/fmg-components/save-progress-popup-question/save-progress-popup-question";
|
||||||
|
import oemGlassQuestion from "./oem-glass-question/oem-glass-question.vue";
|
||||||
|
|
||||||
// Supporting files
|
// Supporting files
|
||||||
import baseMixin from "@/mixins/base-mixin.js";
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
|
|
@ -217,6 +226,15 @@ export default {
|
||||||
null,
|
null,
|
||||||
"quote"
|
"quote"
|
||||||
);
|
);
|
||||||
|
const experimentAllowsOfferOem = experimentMixin.methods.hasSettingEqualTo(
|
||||||
|
experimentSettings.OFFER_OEM,
|
||||||
|
"true"
|
||||||
|
);
|
||||||
|
const oemGlassCheckPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||||
|
storeActions.GET_OEM_AFTERMARKET_PARTS,
|
||||||
|
null,
|
||||||
|
"quote"
|
||||||
|
);
|
||||||
|
|
||||||
const shouldExposeSkipQuote = store.getters.isRecalibrationOnOrder;
|
const shouldExposeSkipQuote = store.getters.isRecalibrationOnOrder;
|
||||||
const skipQuoteExperiment = store.getters.applicationUser.experiments.find(
|
const skipQuoteExperiment = store.getters.applicationUser.experiments.find(
|
||||||
|
|
@ -290,6 +308,10 @@ export default {
|
||||||
resultKey: "supportingItems",
|
resultKey: "supportingItems",
|
||||||
promise: supportingItemsPromise,
|
promise: supportingItemsPromise,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
resultKey: "oemGlassCheck",
|
||||||
|
promise: oemGlassCheckPromise,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const resultMap = await settleAllPromises(promiseResultMap);
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
|
|
@ -311,6 +333,18 @@ export default {
|
||||||
lineItems.vaps = lineItems.vaps ?? [];
|
lineItems.vaps = lineItems.vaps ?? [];
|
||||||
const nullSafeGlassParts = lineItems.glassParts ?? [];
|
const nullSafeGlassParts = lineItems.glassParts ?? [];
|
||||||
|
|
||||||
|
const oemGlassCheck = resultMap.oemGlassCheck;
|
||||||
|
const offerOem = experimentAllowsOfferOem ? oemGlassCheck?.offerOem : false;
|
||||||
|
const oemGlassPieceParts = oemGlassCheck?.glassPieceParts;
|
||||||
|
const oemGlassParts =
|
||||||
|
oemGlassPieceParts && oemGlassPieceParts.length > 0
|
||||||
|
? oemGlassPieceParts[0].parts
|
||||||
|
: null;
|
||||||
|
const isOemGlassSelected = store.getters.order.damage?.installOemGlass ?? false;
|
||||||
|
|
||||||
|
let alternateGlassPartsForOem;
|
||||||
|
if (oemGlassParts) alternateGlassPartsForOem = oemGlassParts;
|
||||||
|
|
||||||
const quotePageDiscountPartsInfo = getAvailableQuotePageDiscounts();
|
const quotePageDiscountPartsInfo = getAvailableQuotePageDiscounts();
|
||||||
|
|
||||||
const quotePageDiscountParts = quotePageDiscountPartsInfo.map((partInfo) => ({
|
const quotePageDiscountParts = quotePageDiscountPartsInfo.map((partInfo) => ({
|
||||||
|
|
@ -328,6 +362,18 @@ export default {
|
||||||
...quotePageDiscountParts,
|
...quotePageDiscountParts,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let pricedAlternateGlassParts = null;
|
||||||
|
if (alternateGlassPartsForOem && alternateGlassPartsForOem.length > 0) {
|
||||||
|
pricedAlternateGlassParts = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||||
|
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||||
|
{
|
||||||
|
availableLineItems: alternateGlassPartsForOem,
|
||||||
|
},
|
||||||
|
"quote",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// When calling pricing from the quote page, always use the cash parent account but save the existing one in case it's unverified insurance navigating backwards
|
// When calling pricing from the quote page, always use the cash parent account but save the existing one in case it's unverified insurance navigating backwards
|
||||||
var holdParentAccountNumber = store.getters.order.payment?.parentAccountNumber;
|
var holdParentAccountNumber = store.getters.order.payment?.parentAccountNumber;
|
||||||
var holdBillToAccountNumber = store.getters.order.payment?.billToAccountNumber;
|
var holdBillToAccountNumber = store.getters.order.payment?.billToAccountNumber;
|
||||||
|
|
@ -389,6 +435,41 @@ export default {
|
||||||
vm.holdParentAccountNumber = holdParentAccountNumber;
|
vm.holdParentAccountNumber = holdParentAccountNumber;
|
||||||
vm.holdBillToAccountNumber = holdBillToAccountNumber;
|
vm.holdBillToAccountNumber = holdBillToAccountNumber;
|
||||||
vm.skipToInsurance = shouldSkipToInsurance;
|
vm.skipToInsurance = shouldSkipToInsurance;
|
||||||
|
vm.offerOem = offerOem;
|
||||||
|
vm.alternateGlassPartsForOem = pricedAlternateGlassParts; // oemParts
|
||||||
|
vm.isOemGlassSelected = isOemGlassSelected;
|
||||||
|
|
||||||
|
const getOEMGlassPartPrice = () => {
|
||||||
|
let price = 0;
|
||||||
|
if (vm.alternateGlassPartsForOem) {
|
||||||
|
let oemGlassPart = vm.isOemGlassSelected
|
||||||
|
? vm.lineItems.glassParts
|
||||||
|
: vm.alternateGlassPartsForOem;
|
||||||
|
if (oemGlassPart) {
|
||||||
|
price =
|
||||||
|
oemGlassPart.find(
|
||||||
|
(altPart) => altPart.partType === partTypeStrings.WINDSHIELD
|
||||||
|
)?.sellingPrice || 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return price;
|
||||||
|
};
|
||||||
|
const getAftermarketGlassPartPrice = () => {
|
||||||
|
let price = 0;
|
||||||
|
if (vm.alternateGlassPartsForOem) {
|
||||||
|
let aftermarketGlassPart = vm.isOemGlassSelected
|
||||||
|
? vm.alternateGlassPartsForOem
|
||||||
|
: vm.lineItems.glassParts;
|
||||||
|
if (aftermarketGlassPart) {
|
||||||
|
price = aftermarketGlassPart.find(
|
||||||
|
(altPart) => altPart.partType === partTypeStrings.WINDSHIELD
|
||||||
|
)?.sellingPrice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return price;
|
||||||
|
};
|
||||||
|
vm.oemGlassPartPrice = getOEMGlassPartPrice();
|
||||||
|
vm.aftermarketGlassPartPrice = getAftermarketGlassPartPrice();
|
||||||
|
|
||||||
if (revalidatePromoResponse) {
|
if (revalidatePromoResponse) {
|
||||||
const revalidateAlerts = buildToastMessagesFromRevalidateOrValidatePromoResponse(
|
const revalidateAlerts = buildToastMessagesFromRevalidateOrValidatePromoResponse(
|
||||||
|
|
@ -556,6 +637,8 @@ export default {
|
||||||
availableLineItems: null,
|
availableLineItems: null,
|
||||||
lineItems: [],
|
lineItems: [],
|
||||||
addableVaps: [],
|
addableVaps: [],
|
||||||
|
offerOem: null,
|
||||||
|
alternateGlassPartsForOem: null,
|
||||||
servicePackage: null,
|
servicePackage: null,
|
||||||
holdParentAccountNumber: null,
|
holdParentAccountNumber: null,
|
||||||
holdBillToAccountNumber: null,
|
holdBillToAccountNumber: null,
|
||||||
|
|
@ -564,6 +647,8 @@ export default {
|
||||||
isSaveProgressComplete: null,
|
isSaveProgressComplete: null,
|
||||||
packageNumber: null,
|
packageNumber: null,
|
||||||
skipToInsurance: null,
|
skipToInsurance: null,
|
||||||
|
servicePackageSelected: null,
|
||||||
|
isOemGlassSelected: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
|
@ -670,6 +755,9 @@ export default {
|
||||||
vapsItemsSelectedAction(vapsItemsSelected) {
|
vapsItemsSelectedAction(vapsItemsSelected) {
|
||||||
this.lineItems.vaps = vapsItemsSelected;
|
this.lineItems.vaps = vapsItemsSelected;
|
||||||
},
|
},
|
||||||
|
servicePackageSelectedAction(newValue) {
|
||||||
|
this.servicePackageSelected = newValue;
|
||||||
|
},
|
||||||
quotePageDiscountSelectedAction(supportingItemsSelected) {
|
quotePageDiscountSelectedAction(supportingItemsSelected) {
|
||||||
this.lineItems.supportingItems = supportingItemsSelected;
|
this.lineItems.supportingItems = supportingItemsSelected;
|
||||||
},
|
},
|
||||||
|
|
@ -725,6 +813,20 @@ export default {
|
||||||
this.lineItems.supportingItems = this.filterOutFees(this.lineItems.supportingItems);
|
this.lineItems.supportingItems = this.filterOutFees(this.lineItems.supportingItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.lineItems.glassParts &&
|
||||||
|
this.alternateGlassPartsForOem &&
|
||||||
|
this.alternateGlassPartsForOem.length > 0
|
||||||
|
) {
|
||||||
|
let isOEMGlassOnOrder = this.isOEMGlassOnOrder(this.lineItems.glassParts);
|
||||||
|
if (
|
||||||
|
(this.isOemGlassSelected && !isOEMGlassOnOrder) ||
|
||||||
|
(!this.isOemGlassSelected && isOEMGlassOnOrder)
|
||||||
|
) {
|
||||||
|
this.swapOEMGlassPartsOnOrder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.lineItems.glassParts?.length > 0) {
|
if (this.lineItems.glassParts?.length > 0) {
|
||||||
this.dispatchStoreAction(
|
this.dispatchStoreAction(
|
||||||
this.storeActions.SAVE_GLASS_PARTS_SUPPRESSING_STATE_RESETTING,
|
this.storeActions.SAVE_GLASS_PARTS_SUPPRESSING_STATE_RESETTING,
|
||||||
|
|
@ -757,8 +859,20 @@ export default {
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
this.dispatchStoreAction(
|
||||||
|
this.storeActions.SAVE_IS_OEM_GLASS_SELECTED,
|
||||||
|
this.isOemGlassSelected,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
await savePageData(this.pageName, { saveProgressPopupSkipped: true }, true);
|
await savePageData(
|
||||||
|
this.pageName,
|
||||||
|
{
|
||||||
|
saveProgressPopupSkipped: true,
|
||||||
|
servicePackageSelected: this.servicePackageSelected,
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
const payment = this.$store.getters.payment;
|
const payment = this.$store.getters.payment;
|
||||||
if (payment.isInsurance) {
|
if (payment.isInsurance) {
|
||||||
|
|
@ -868,6 +982,26 @@ export default {
|
||||||
const alert = createPromoSuccessAlert(lineItems.promos[0].promoCode);
|
const alert = createPromoSuccessAlert(lineItems.promos[0].promoCode);
|
||||||
this.$refs.funnelHeader.pushGlobalAlert(alert, alert.shouldAutoFade);
|
this.$refs.funnelHeader.pushGlobalAlert(alert, alert.shouldAutoFade);
|
||||||
},
|
},
|
||||||
|
isOEMGlassOnOrder(glassParts) {
|
||||||
|
if (glassParts && glassParts.length > 0) {
|
||||||
|
return glassParts.some((part) => part.partNumber.endsWith("OEM"));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
swapOEMGlassPartsOnOrder() {
|
||||||
|
this.lineItems.glassParts.forEach((part, index) => {
|
||||||
|
if (part.partType === partTypeStrings.WINDSHIELD) {
|
||||||
|
let altPartToUse = this.alternateGlassPartsForOem.find(
|
||||||
|
(altPart) => altPart.partType === partTypeStrings.WINDSHIELD
|
||||||
|
);
|
||||||
|
if (altPartToUse) {
|
||||||
|
let childParts = this.lineItems.glassParts[index].childParts;
|
||||||
|
altPartToUse.childParts = childParts;
|
||||||
|
this.lineItems.glassParts[index] = altPartToUse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
funnelHeader,
|
funnelHeader,
|
||||||
|
|
@ -884,6 +1018,7 @@ export default {
|
||||||
recalDisclaimer,
|
recalDisclaimer,
|
||||||
saveProgressModalQuestion,
|
saveProgressModalQuestion,
|
||||||
saveProgressPopupQuestion,
|
saveProgressPopupQuestion,
|
||||||
|
oemGlassQuestion,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import {
|
||||||
findLineItemsWithPartType,
|
findLineItemsWithPartType,
|
||||||
getPackageNameByType,
|
getPackageNameByType,
|
||||||
getDiscountedPackageName,
|
getDiscountedPackageName,
|
||||||
|
getPackageLabel,
|
||||||
} from "@/helpers/service-package-helper";
|
} from "@/helpers/service-package-helper";
|
||||||
import {
|
import {
|
||||||
getPromosThatMatchLineItemsOnOrder,
|
getPromosThatMatchLineItemsOnOrder,
|
||||||
|
|
@ -45,6 +46,7 @@ import { containsRecalParts, getItemsWithoutRecalParts } from "@/helpers/recal-h
|
||||||
import experimentMixin from "@/mixins/experiment-mixin.js";
|
import experimentMixin from "@/mixins/experiment-mixin.js";
|
||||||
import { experimentSettings } from "@/constants/experiments";
|
import { experimentSettings } from "@/constants/experiments";
|
||||||
import { savePageData } from "@/router/methods/helpers/save-page-data";
|
import { savePageData } from "@/router/methods/helpers/save-page-data";
|
||||||
|
import { debugLog } from "@/helpers/debug-log-helper";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "servicePackageQuestion",
|
name: "servicePackageQuestion",
|
||||||
|
|
@ -84,6 +86,12 @@ export default {
|
||||||
if (this.activePromos?.length) this.selectDefaultPackage();
|
if (this.activePromos?.length) this.selectDefaultPackage();
|
||||||
},
|
},
|
||||||
selectedPackageName(newValue) {
|
selectedPackageName(newValue) {
|
||||||
|
debugLog("selectedPackageName:", newValue);
|
||||||
|
const packageLabel = getPackageLabel(newValue);
|
||||||
|
|
||||||
|
debugLog("getPackageLabel:", packageLabel);
|
||||||
|
this.$emit("servicePackageSelected", `ServicePackage-${packageLabel}`);
|
||||||
|
|
||||||
const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue);
|
const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue);
|
||||||
this.$emit("vapsItemsSelected", VapsProductsInSelectedPackage);
|
this.$emit("vapsItemsSelected", VapsProductsInSelectedPackage);
|
||||||
const discountPartInSelectedPackage = this.getDiscountPartForSelectedPackage(newValue);
|
const discountPartInSelectedPackage = this.getDiscountPartForSelectedPackage(newValue);
|
||||||
|
|
|
||||||
|
|
@ -72,10 +72,10 @@ export function isDropOffRouteCode(routeCode) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTodayDate(routeCode) {
|
export function getTodayDate() {
|
||||||
return new Date();
|
return new Date();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTodayDateString(routeCode) {
|
export function getTodayDateString() {
|
||||||
return convertDateToDateString(getTodayDate());
|
return convertDateToDateString(getTodayDate());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isModalOpen: false,
|
isModalOpen: false,
|
||||||
selectedAppontment: {
|
selectedAppointment: {
|
||||||
estimatedServiceMinutes: {
|
estimatedServiceMinutes: {
|
||||||
minimum: null,
|
minimum: null,
|
||||||
maximum: null,
|
maximum: null,
|
||||||
|
|
@ -58,6 +58,7 @@ export default {
|
||||||
timeSlot: null,
|
timeSlot: null,
|
||||||
date: null,
|
date: null,
|
||||||
},
|
},
|
||||||
|
confirmedAppointment: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -67,22 +68,25 @@ export default {
|
||||||
this.modal.openModal();
|
this.modal.openModal();
|
||||||
},
|
},
|
||||||
onModalClosed() {
|
onModalClosed() {
|
||||||
|
this.pushEvent();
|
||||||
this.$emit("close-mobile-first-modal");
|
this.$emit("close-mobile-first-modal");
|
||||||
|
this.confirmedAppointment = false;
|
||||||
},
|
},
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.modal.closeModal();
|
this.modal.closeModal();
|
||||||
},
|
},
|
||||||
confirmAppointment() {
|
confirmAppointment() {
|
||||||
let selectedTimeSlot = {
|
let selectedTimeSlot = {
|
||||||
timeSlot: this.selectedAppontment.timeSlot,
|
timeSlot: this.selectedAppointment.timeSlot,
|
||||||
routeCode: this.selectedAppontment.timeSlot.id,
|
routeCode: this.selectedAppointment.timeSlot.id,
|
||||||
date: this.selectedAppontment.date,
|
date: this.selectedAppointment.date,
|
||||||
};
|
};
|
||||||
|
this.confirmedAppointment = true;
|
||||||
this.$emit("confirm-appointment", selectedTimeSlot);
|
this.$emit("confirm-appointment", selectedTimeSlot);
|
||||||
this.modal.closeModal();
|
this.modal.closeModal();
|
||||||
},
|
},
|
||||||
setSelectedAppointment(appointment) {
|
setSelectedAppointment(appointment) {
|
||||||
this.selectedAppontment = appointment;
|
this.selectedAppointment = appointment;
|
||||||
},
|
},
|
||||||
getIsModalOpen() {
|
getIsModalOpen() {
|
||||||
return this.isModalOpen;
|
return this.isModalOpen;
|
||||||
|
|
@ -90,6 +94,21 @@ export default {
|
||||||
setIsModalOpen(isOpen) {
|
setIsModalOpen(isOpen) {
|
||||||
this.isModalOpen = isOpen;
|
this.isModalOpen = isOpen;
|
||||||
},
|
},
|
||||||
|
pushEvent() {
|
||||||
|
let gaAction = this.confirmedAppointment ? this.GaActions.YES : this.GaActions.NO;
|
||||||
|
if (this.selectedAppointment && this.selectedAppointment.timeSlot) {
|
||||||
|
this.pushEventToGA(
|
||||||
|
this.GaCategories.CONFIRMATION_CLICKED,
|
||||||
|
gaAction,
|
||||||
|
this.selectedAppointment.timeSlot.startTime +
|
||||||
|
"-" +
|
||||||
|
this.selectedAppointment.timeSlot.endTime +
|
||||||
|
" " +
|
||||||
|
this.selectedAppointment.date,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
modalName() {
|
modalName() {
|
||||||
|
|
@ -112,37 +131,37 @@ export default {
|
||||||
if (token.includes(INLINE_SERVICETYPE_TOKEN)) {
|
if (token.includes(INLINE_SERVICETYPE_TOKEN)) {
|
||||||
const serviceType = "<strong>" + this.getServiceType + "</strong>";
|
const serviceType = "<strong>" + this.getServiceType + "</strong>";
|
||||||
tokens.splice(tokens.indexOf(token), 1, serviceType);
|
tokens.splice(tokens.indexOf(token), 1, serviceType);
|
||||||
} else if (token.includes(INLINE_DAY_TOKEN) && this.selectedAppontment.date) {
|
} else if (token.includes(INLINE_DAY_TOKEN) && this.selectedAppointment.date) {
|
||||||
const day =
|
const day =
|
||||||
"<strong>" +
|
"<strong>" +
|
||||||
new Date(this.selectedAppontment.date).toLocaleDateString("en-US", {
|
new Date(this.selectedAppointment.date).toLocaleDateString("en-US", {
|
||||||
weekday: "long",
|
weekday: "long",
|
||||||
timeZone: "UTC",
|
timeZone: "UTC",
|
||||||
}) +
|
}) +
|
||||||
"</strong>";
|
"</strong>";
|
||||||
tokens.splice(tokens.indexOf(token), 1, day);
|
tokens.splice(tokens.indexOf(token), 1, day);
|
||||||
} else if (token.includes(INLINE_DATE_TOKEN) && this.selectedAppontment.date) {
|
} else if (token.includes(INLINE_DATE_TOKEN) && this.selectedAppointment.date) {
|
||||||
const date = new Date(this.selectedAppontment.date).toLocaleDateString(
|
const date = new Date(this.selectedAppointment.date).toLocaleDateString(
|
||||||
"en-US",
|
"en-US",
|
||||||
{ timeZone: "UTC", weekday: "long", month: "long", day: "numeric" }
|
{ timeZone: "UTC", weekday: "long", month: "long", day: "numeric" }
|
||||||
);
|
);
|
||||||
tokens.splice(tokens.indexOf(token), 1, date);
|
tokens.splice(tokens.indexOf(token), 1, date);
|
||||||
} else if (
|
} else if (
|
||||||
token.includes(INLINE_TIMESLOT_TOKEN) &&
|
token.includes(INLINE_TIMESLOT_TOKEN) &&
|
||||||
this.selectedAppontment.timeSlot
|
this.selectedAppointment.timeSlot
|
||||||
) {
|
) {
|
||||||
const timeslot =
|
const timeslot =
|
||||||
this.get12HourTimeFormat(this.selectedAppontment.timeSlot.startTime) +
|
this.get12HourTimeFormat(this.selectedAppointment.timeSlot.startTime) +
|
||||||
" - " +
|
" - " +
|
||||||
this.get12HourTimeFormat(this.selectedAppontment.timeSlot.endTime);
|
this.get12HourTimeFormat(this.selectedAppointment.timeSlot.endTime);
|
||||||
tokens.splice(tokens.indexOf(token), 1, timeslot);
|
tokens.splice(tokens.indexOf(token), 1, timeslot);
|
||||||
} else if (
|
} else if (
|
||||||
token.includes(INLINE_SERVICE_LENGTH_TOKEN) &&
|
token.includes(INLINE_SERVICE_LENGTH_TOKEN) &&
|
||||||
this.selectedAppontment.estimatedServiceMinutes
|
this.selectedAppointment.estimatedServiceMinutes
|
||||||
) {
|
) {
|
||||||
const inshopDurationTime = getDisplayTextForDurationLength(
|
const inshopDurationTime = getDisplayTextForDurationLength(
|
||||||
this.selectedAppontment.estimatedServiceMinutes.minimum,
|
this.selectedAppointment.estimatedServiceMinutes.minimum,
|
||||||
this.selectedAppontment.estimatedServiceMinutes.maximum
|
this.selectedAppointment.estimatedServiceMinutes.maximum
|
||||||
);
|
);
|
||||||
tokens.splice(tokens.indexOf(token), 1, inshopDurationTime);
|
tokens.splice(tokens.indexOf(token), 1, inshopDurationTime);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
223
src/layouts/schedule/recal-ack-modal/recal-ack-modal.vue
Normal file
223
src/layouts/schedule/recal-ack-modal/recal-ack-modal.vue
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
// This modal is used to acknowledge the recal disclaimer during scheduling. // It will trigger when
|
||||||
|
there is a glass part with requiresRecalibration = true // but there is no recalibration part in
|
||||||
|
lineItems. This mostly occurs when // a zip code that does mobile only is selected but the vehicle
|
||||||
|
requires // recalibration.
|
||||||
|
<template>
|
||||||
|
<div id="recal-ack-modal-container">
|
||||||
|
<modal
|
||||||
|
:ref="modalName"
|
||||||
|
suppressPageScroll
|
||||||
|
:onModalClosedCallback="onModalClosed"
|
||||||
|
:footerButtonText="modalFooterText"
|
||||||
|
:isFooterButtonPrimary="true"
|
||||||
|
@footer-button-event="ackRecalDisclaimer">
|
||||||
|
<p class="modal-body-inner" v-html="modalBodyText"></p>
|
||||||
|
<div>
|
||||||
|
<div class="learn-more">
|
||||||
|
<a href="javascript:;" @click="learnMore = !learnMore">{{ learnMoreLabel }}</a>
|
||||||
|
<div v-if="learnMore">{{ learnMoreText }}</div>
|
||||||
|
</div>
|
||||||
|
<buttonQuestion
|
||||||
|
:questionText="questionText"
|
||||||
|
:answers="answersFromCms"
|
||||||
|
:groupName="groupName"
|
||||||
|
buttonTypeString="listButtonHorizontal"
|
||||||
|
v-model="selectedValues"
|
||||||
|
isRequired />
|
||||||
|
</div>
|
||||||
|
<template v-slot:modal-header-slot>
|
||||||
|
<span>{{ modalHeaderText }}</span>
|
||||||
|
</template>
|
||||||
|
</modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import modal from "@/digital-components/modal/modal";
|
||||||
|
import buttonQuestion from "@/digital-components/button-question/button-question";
|
||||||
|
import { RECAL_ACK_YES } from "@/constants/schedule-constants";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "recal-ack-modal",
|
||||||
|
props: {
|
||||||
|
agreementWidgetName: String,
|
||||||
|
modalWidgetName: String,
|
||||||
|
groupName: String,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
learnMore: false,
|
||||||
|
isModalOpen: true,
|
||||||
|
isRecalAcknowledged: "",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openModal() {
|
||||||
|
this.modal.openModal();
|
||||||
|
},
|
||||||
|
onModalClosed() {
|
||||||
|
this.$emit("recal-ack-modal");
|
||||||
|
},
|
||||||
|
closeModal() {
|
||||||
|
this.modal.closeModal();
|
||||||
|
},
|
||||||
|
setIsModalOpen(isOpen) {
|
||||||
|
this.isModalOpen = isOpen;
|
||||||
|
},
|
||||||
|
ackRecalDisclaimer() {
|
||||||
|
this.modal.closeModal();
|
||||||
|
|
||||||
|
if (this.isRecalAcknowledged === RECAL_ACK_YES) {
|
||||||
|
this.pushEventToGA(
|
||||||
|
this.GaCategories.RECAL_DISCLAIMER,
|
||||||
|
this.GaActions.ACKNOWLEDGED,
|
||||||
|
this.isRecalAcknowledged,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
this.$emit("recalAcknowledged", this.isRecalAcknowledged);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
questionText() {
|
||||||
|
return this.getCmsContent(this.agreementWidgetName, "QuestionText");
|
||||||
|
},
|
||||||
|
answersFromCms() {
|
||||||
|
return this.getCmsContent(this.agreementWidgetName, "Answers");
|
||||||
|
},
|
||||||
|
learnMoreLabel() {
|
||||||
|
return this.getCmsContent(this.modalWidgetName, "SubheaderText");
|
||||||
|
},
|
||||||
|
learnMoreText() {
|
||||||
|
return this.getCmsContent(this.modalWidgetName, "FooterText2");
|
||||||
|
},
|
||||||
|
modalName() {
|
||||||
|
return this.modalWidgetName;
|
||||||
|
},
|
||||||
|
modal() {
|
||||||
|
return this.$refs[this.modalName];
|
||||||
|
},
|
||||||
|
modalHeaderText() {
|
||||||
|
return this.getCmsContent(this.modalWidgetName, "HeaderText");
|
||||||
|
},
|
||||||
|
modalBodyText() {
|
||||||
|
return this.getCmsContent(this.modalWidgetName, "BodyText");
|
||||||
|
},
|
||||||
|
modalFooterText() {
|
||||||
|
return this.getCmsContent(this.modalWidgetName, "FooterText");
|
||||||
|
},
|
||||||
|
selectedValues: {
|
||||||
|
get: function () {
|
||||||
|
return this.isRecalAcknowledged;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
this.isRecalAcknowledged = this.answersFromCms.find(
|
||||||
|
(a) => a.Name === newValue
|
||||||
|
)?.Text;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { modal, buttonQuestion },
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
#recal-ack-modal-container {
|
||||||
|
.modal-component {
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
.modal-dialog {
|
||||||
|
left: 0;
|
||||||
|
align-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 34.875rem;
|
||||||
|
transform: translate(0, 0);
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
border-radius: $border-radius-lg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modal-dialog {
|
||||||
|
.modal-content {
|
||||||
|
.btn-close {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.modal-header {
|
||||||
|
width: 75%;
|
||||||
|
padding: 0 1.25rem 0 1.25rem;
|
||||||
|
flex-direction: column;
|
||||||
|
& > span {
|
||||||
|
color: $black;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: $font-size-20;
|
||||||
|
font-weight: $font-weight-normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modal-body {
|
||||||
|
padding: 0.875rem 1.25rem 1.875rem 1.25rem;
|
||||||
|
|
||||||
|
.modal-body-inner {
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
font-size: $font-size-14;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-inner-wrapper > .col:first-child .list-button-horizontal {
|
||||||
|
border-bottom-left-radius: 50rem;
|
||||||
|
border-top-left-radius: 50rem;
|
||||||
|
}
|
||||||
|
.col:first-of-type .list-button-horizontal .list-button-horizontal-content,
|
||||||
|
.list-group:first-of-type
|
||||||
|
.list-button-horizontal
|
||||||
|
.list-button-horizontal-content {
|
||||||
|
border-bottom-left-radius: 50rem;
|
||||||
|
border-top-left-radius: 50rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-inner-wrapper > .col:last-child .list-button-horizontal {
|
||||||
|
border-bottom-right-radius: 50rem;
|
||||||
|
border-top-right-radius: 50rem;
|
||||||
|
}
|
||||||
|
.col:last-of-type .list-button-horizontal .list-button-horizontal-content,
|
||||||
|
.list-group:first-of-type
|
||||||
|
.list-button-horizontal
|
||||||
|
.list-button-horizontal-content {
|
||||||
|
border-bottom-right-radius: 50rem;
|
||||||
|
border-top-right-radius: 50rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learn-more {
|
||||||
|
padding-bottom: 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
a {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.question-text {
|
||||||
|
margin-top: 0.875rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modal-footer {
|
||||||
|
flex-flow: wrap-reverse;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.modal-footer .modal-footer-button:hover {
|
||||||
|
background-color: $red-1000;
|
||||||
|
border-color: $red-1100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
183
src/layouts/schedule/schedule.spec.js
Normal file
183
src/layouts/schedule/schedule.spec.js
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
// Components
|
||||||
|
import schedule from "@/layouts/schedule/schedule.vue";
|
||||||
|
|
||||||
|
// Supporting Files
|
||||||
|
import { shallowMount } from "@vue/test-utils";
|
||||||
|
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||||
|
import { routeData } from "@/router/constants/routes";
|
||||||
|
import store from "@/store";
|
||||||
|
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||||
|
|
||||||
|
const storeMocked = {
|
||||||
|
getters: {
|
||||||
|
payment: { isInsurance: false },
|
||||||
|
order: {
|
||||||
|
payment: { isInsurance: false, insuranceCoverage: { isVerified: false } },
|
||||||
|
referralNumber: "",
|
||||||
|
serviceLocation: {
|
||||||
|
address: "",
|
||||||
|
address2: "",
|
||||||
|
city: "",
|
||||||
|
state: "",
|
||||||
|
zipCode: "00000",
|
||||||
|
zipCodeCtu: "000",
|
||||||
|
appointmentType: null,
|
||||||
|
provider: {},
|
||||||
|
isVehicleProtected: false,
|
||||||
|
},
|
||||||
|
schedule: {
|
||||||
|
date: "",
|
||||||
|
routeCode: "",
|
||||||
|
startTime: "",
|
||||||
|
endTime: "",
|
||||||
|
jobMinMinutes: null,
|
||||||
|
jobMaxMinutes: null,
|
||||||
|
},
|
||||||
|
policy: {
|
||||||
|
isItac: false,
|
||||||
|
isNoComp: false,
|
||||||
|
},
|
||||||
|
damage: { isRepair: false },
|
||||||
|
lineItems: { glassParts: [] },
|
||||||
|
},
|
||||||
|
vehicle: {
|
||||||
|
cardId: "",
|
||||||
|
},
|
||||||
|
lineItems: {
|
||||||
|
supportingItems: [],
|
||||||
|
},
|
||||||
|
applicationUser: {
|
||||||
|
experiments: [],
|
||||||
|
},
|
||||||
|
isMobileAppointment: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock("@/helpers/heritage-integration/navigation-helper", () => ({
|
||||||
|
navigateToHeritageFunnel: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("schedule.vue", () => {
|
||||||
|
describe("navigation", () => {
|
||||||
|
test("backButtonAction => non-insurance: navigateWithoutSaving called", () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper, mocks } = setupMocks();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.backButtonAction();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
|
||||||
|
mocks.navigationScenarios.CLICKED_BACK,
|
||||||
|
routeData.SCHEDULE.name
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("backButtonAction => insurance verified: navigateToHeritageFunnel called", () => {
|
||||||
|
// Arrange
|
||||||
|
store.getters.order.payment.isInsurance = true;
|
||||||
|
store.getters.order.payment.insuranceCoverage = { isVerified: true };
|
||||||
|
store.getters.order.referralNumber = "";
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.backButtonAction();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(navigateToHeritageFunnel).toHaveBeenCalledWith({
|
||||||
|
shouldSaveSession: false,
|
||||||
|
pageNameToLog: "schedule",
|
||||||
|
navType: "back",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("computed: isForwardActionDisabled", () => {
|
||||||
|
test("should be true when routeCode missing or appointmentType not set", async () => {
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
await wrapper.setData({
|
||||||
|
selectedTimeSlotInfo: { timeSlot: { routeCode: null } },
|
||||||
|
appointmentType: null,
|
||||||
|
});
|
||||||
|
expect(wrapper.vm.isForwardActionDisabled).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should be false when routeCode present and appointmentType set", async () => {
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
await wrapper.setData({
|
||||||
|
selectedTimeSlotInfo: { timeSlot: { routeCode: "RC123" } },
|
||||||
|
appointmentType: "SomeType",
|
||||||
|
});
|
||||||
|
expect(wrapper.vm.isForwardActionDisabled).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("computed: selectedRouteCodeData", () => {
|
||||||
|
test("should return null when routeCode not set", async () => {
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
await wrapper.setData({
|
||||||
|
selectedTimeSlotInfo: { timeSlot: { routeCode: null } },
|
||||||
|
});
|
||||||
|
expect(wrapper.vm.selectedRouteCodeData).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return routeCode and premium flag when set", async () => {
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
await wrapper.setData({
|
||||||
|
selectedTimeSlotInfo: {
|
||||||
|
timeSlot: { routeCode: "RC999" },
|
||||||
|
isPremiumAppointment: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(wrapper.vm.selectedRouteCodeData).toEqual({
|
||||||
|
routeCode: "RC999",
|
||||||
|
isPremiumAppointment: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("forwardButtonAction (early exit path)", () => {
|
||||||
|
test("shows recal acknowledgement popup and does not navigate when required", async () => {
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
// Force the recal-ack modal path
|
||||||
|
wrapper.vm.showRecalAcknowledgementModal = jest.fn(() => true);
|
||||||
|
await wrapper.setData({ isRecalAcknowledgedForScheduling: null });
|
||||||
|
|
||||||
|
await wrapper.vm.forwardButtonAction();
|
||||||
|
|
||||||
|
expect(wrapper.vm.showRecalAcknowledgementPopup).toBe(true);
|
||||||
|
expect(wrapper.vm.$router.navigateWithSaving).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupMocks(mountOptionsMockData = {}) {
|
||||||
|
const route = { name: "schedule" };
|
||||||
|
const defaultMountOptions = {
|
||||||
|
route: route,
|
||||||
|
router: {
|
||||||
|
navigate: jest.fn(),
|
||||||
|
navigateWithSaving: jest.fn(),
|
||||||
|
navigateWithoutSaving: jest.fn(),
|
||||||
|
},
|
||||||
|
store: { ...storeMocked },
|
||||||
|
};
|
||||||
|
const mockBaseMixin = {
|
||||||
|
methods: {
|
||||||
|
getTierOnePackagePrice: jest.fn(),
|
||||||
|
filterOutFees: jest.fn(),
|
||||||
|
getCmsContent: jest.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const baseMountOptions = getMountOptions({
|
||||||
|
...defaultMountOptions,
|
||||||
|
...mountOptionsMockData,
|
||||||
|
});
|
||||||
|
|
||||||
|
baseMountOptions.global.mixins = [mockBaseMixin];
|
||||||
|
baseMountOptions.global.mocks.pageName = route.name;
|
||||||
|
const wrapper = shallowMount(schedule, baseMountOptions);
|
||||||
|
wrapper.getCmsContent = jest.fn();
|
||||||
|
|
||||||
|
return { wrapper, mocks: baseMountOptions.global?.mocks || {} };
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,12 @@
|
||||||
modalWidgetName="MobileFirstModalWidget"
|
modalWidgetName="MobileFirstModalWidget"
|
||||||
ref="mobileFirstModal"
|
ref="mobileFirstModal"
|
||||||
@confirm-appointment="updateTimeSlotandNavigateForward" />
|
@confirm-appointment="updateTimeSlotandNavigateForward" />
|
||||||
|
<recalAckModal
|
||||||
|
modalWidgetName="RecalAckModalWidget"
|
||||||
|
agreementWidgetName="RecalAgreementQuestionWidget"
|
||||||
|
groupName="recalAckGroup"
|
||||||
|
@recalAcknowledged="updateRecalAcknowledgedAndNavigateForward"
|
||||||
|
ref="recalAckModal" />
|
||||||
<div
|
<div
|
||||||
class="container page-container-grouped-styles"
|
class="container page-container-grouped-styles"
|
||||||
:class="[isMobileFirstModalOpen ? 'hidden-background' : '']">
|
:class="[isMobileFirstModalOpen ? 'hidden-background' : '']">
|
||||||
|
|
@ -22,6 +28,7 @@
|
||||||
cmsWidgetName="ScheduleYourServiceWidget"
|
cmsWidgetName="ScheduleYourServiceWidget"
|
||||||
id="schedule-your-service" />
|
id="schedule-your-service" />
|
||||||
<appointmentTypeQuestion
|
<appointmentTypeQuestion
|
||||||
|
v-if="hasSelectableDatesLoaded"
|
||||||
v-model="appointmentTypeFromAppointmentTypeQuestion"
|
v-model="appointmentTypeFromAppointmentTypeQuestion"
|
||||||
v-show="isAppointmentTypeDisplayed"
|
v-show="isAppointmentTypeDisplayed"
|
||||||
:isServiceableMobile="isServiceableMobile"
|
:isServiceableMobile="isServiceableMobile"
|
||||||
|
|
@ -91,15 +98,11 @@
|
||||||
linkWidgetName="ServiceZipLinkWidget"
|
linkWidgetName="ServiceZipLinkWidget"
|
||||||
modalWidgetName="ServiceZipModalWidget" />
|
modalWidgetName="ServiceZipModalWidget" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="row your-shop-location" v-if="showShopLocationComponent">
|
||||||
class="row your-shop-location"
|
|
||||||
v-if="appointmentType && appointmentType !== appointmentTypeStrings.MOBILE">
|
|
||||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||||
<div>
|
<div>
|
||||||
<shopLocation
|
<shopLocation
|
||||||
v-if="
|
v-if="showShopLocationComponent"
|
||||||
appointmentType && appointmentType !== appointmentTypeStrings.MOBILE
|
|
||||||
"
|
|
||||||
:selectedShop="selectedShopAnswer"
|
:selectedShop="selectedShopAnswer"
|
||||||
:selectedProviderNumberFromParent="selectedProvider?.providerNumber"
|
:selectedProviderNumberFromParent="selectedProvider?.providerNumber"
|
||||||
:shopProviderDataFromParent="shopProviderData"
|
:shopProviderDataFromParent="shopProviderData"
|
||||||
|
|
@ -133,8 +136,6 @@
|
||||||
:isOvernightDropoff="isOvernightDropoff" />
|
:isOvernightDropoff="isOvernightDropoff" />
|
||||||
</div>
|
</div>
|
||||||
<datePicker
|
<datePicker
|
||||||
:currentZip="zipCode"
|
|
||||||
:currentProviderNumber="selectedProvider?.providerNumber"
|
|
||||||
v-show="appointmentType"
|
v-show="appointmentType"
|
||||||
customComponentId="dateQuestion"
|
customComponentId="dateQuestion"
|
||||||
selectableDatesSetting="custom"
|
selectableDatesSetting="custom"
|
||||||
|
|
@ -144,11 +145,12 @@
|
||||||
class="text-link-small"
|
class="text-link-small"
|
||||||
:getMoreDatesCallback="getMoreScheduleData"
|
:getMoreDatesCallback="getMoreScheduleData"
|
||||||
validationRules="date-required"
|
validationRules="date-required"
|
||||||
@date-clicked="handleDateClicked"
|
@date-selected="handleDateSelected"
|
||||||
:pricingByDayBasePrice="pricingByDayBasePrice"
|
:pricingByDayBasePrice="pricingByDayBasePrice"
|
||||||
:pricingByDayUpcharge="pricingByDayUpcharge"
|
:pricingByDayUpcharge="pricingByDayUpcharge"
|
||||||
:showPricingByDay="showPricingByDay"
|
:showPricingByDay="showPricingByDay"
|
||||||
:isPricingByDayExperiment="isPricingByDayExperiment" />
|
:isPricingByDayExperiment="isPricingByDayExperiment"
|
||||||
|
:loadingStatus="calendarLoadingStatus" />
|
||||||
<timeSlotQuestion
|
<timeSlotQuestion
|
||||||
ref="timeSlotModalQuestion"
|
ref="timeSlotModalQuestion"
|
||||||
v-if="showTimeSlotQuestion"
|
v-if="showTimeSlotQuestion"
|
||||||
|
|
@ -167,7 +169,8 @@
|
||||||
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
|
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
|
||||||
:isSameDay="isSameDay"
|
:isSameDay="isSameDay"
|
||||||
:selectedRouteCodeData="selectedRouteCodeData"
|
:selectedRouteCodeData="selectedRouteCodeData"
|
||||||
@waitListRequested="handleWaitListRequested" />
|
@waitListRequested="handleWaitListRequested"
|
||||||
|
:lazyLoadAmountToLoad="10" />
|
||||||
<navbar
|
<navbar
|
||||||
cmsWidgetName="FunnelFooterWidget"
|
cmsWidgetName="FunnelFooterWidget"
|
||||||
ref="navbar"
|
ref="navbar"
|
||||||
|
|
@ -190,6 +193,7 @@ import shopLocation from "@/layouts/schedule/shop-location/shop-location";
|
||||||
import timeSlotQuestion from "@/layouts/schedule/time-slot-question/time-slot-question.vue";
|
import timeSlotQuestion from "@/layouts/schedule/time-slot-question/time-slot-question.vue";
|
||||||
import durationTextBlock from "@/layouts/schedule/duration-text-block/duration-text-block.vue";
|
import durationTextBlock from "@/layouts/schedule/duration-text-block/duration-text-block.vue";
|
||||||
import mobileFirstModal from "@/layouts/schedule/mobile-first-modal/mobile-first-modal.vue";
|
import mobileFirstModal from "@/layouts/schedule/mobile-first-modal/mobile-first-modal.vue";
|
||||||
|
import recalAckModal from "@/layouts/schedule/recal-ack-modal/recal-ack-modal.vue";
|
||||||
|
|
||||||
import shopQuestion from "@/layouts/service-location/shop-question/shop-question";
|
import shopQuestion from "@/layouts/service-location/shop-question/shop-question";
|
||||||
import shopQuestionPopup from "@/layouts/service-location/shop-question/shop-question-popup";
|
import shopQuestionPopup from "@/layouts/service-location/shop-question/shop-question-popup";
|
||||||
|
|
@ -216,6 +220,7 @@ import {
|
||||||
RouteCodeFlags,
|
RouteCodeFlags,
|
||||||
PREMIUM_FEE_PART_TYPE,
|
PREMIUM_FEE_PART_TYPE,
|
||||||
PRICING_BY_DAY_PART_TYPE,
|
PRICING_BY_DAY_PART_TYPE,
|
||||||
|
RECAL_ACK_YES,
|
||||||
} from "@/constants/schedule-constants";
|
} from "@/constants/schedule-constants";
|
||||||
|
|
||||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
|
|
@ -242,6 +247,7 @@ import {
|
||||||
isDropOffRouteCode,
|
isDropOffRouteCode,
|
||||||
getTodayDate,
|
getTodayDate,
|
||||||
} from "@/layouts/schedule/helpers/schedule-helper";
|
} from "@/layouts/schedule/helpers/schedule-helper";
|
||||||
|
import { containsRecalParts, anyPartWithRequiresRecalFlag } from "@/helpers/recal-helper";
|
||||||
|
|
||||||
import { DAYS_OF_WEEK } from "@/digital-components/date-picker/mixins/constants";
|
import { DAYS_OF_WEEK } from "@/digital-components/date-picker/mixins/constants";
|
||||||
import { required } from "@/helpers/validation-rules";
|
import { required } from "@/helpers/validation-rules";
|
||||||
|
|
@ -287,6 +293,7 @@ const getScheduleApiResponse = async ({
|
||||||
startDateString,
|
startDateString,
|
||||||
endDateString,
|
endDateString,
|
||||||
providerNumber,
|
providerNumber,
|
||||||
|
inshopProviderNumber,
|
||||||
zipCode,
|
zipCode,
|
||||||
includeMobileTimeSlots = true,
|
includeMobileTimeSlots = true,
|
||||||
includeInshopTimeSlots = true,
|
includeInshopTimeSlots = true,
|
||||||
|
|
@ -329,7 +336,7 @@ const getScheduleApiResponse = async ({
|
||||||
startDate: apiStartDate,
|
startDate: apiStartDate,
|
||||||
endDate: apiEndDate,
|
endDate: apiEndDate,
|
||||||
shopAppointmentType: "InshopOrDropoff",
|
shopAppointmentType: "InshopOrDropoff",
|
||||||
providerNumber: providerNumber,
|
providerNumber: inshopProviderNumber,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (apiStartDate < apiEndDate) {
|
if (apiStartDate < apiEndDate) {
|
||||||
|
|
@ -429,19 +436,19 @@ export default {
|
||||||
name: "schedule",
|
name: "schedule",
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
isRecalAcknowledgedForScheduling: this.getIsRecalAcknowledgedForSchedulingFromStore(),
|
||||||
selectedDate: this.getSelectedDateFromStore(),
|
selectedDate: this.getSelectedDateFromStore(),
|
||||||
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
|
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
|
||||||
mobilePremiumAppointmentFee: null,
|
mobilePremiumAppointmentFee: null,
|
||||||
waitListRequested: null,
|
waitListRequested: null,
|
||||||
displayWaitList: null,
|
|
||||||
pricingByDayUpchargeLineItem: null,
|
pricingByDayUpchargeLineItem: null,
|
||||||
includePricingByDayUpcharge: null,
|
includePricingByDayUpcharge: null,
|
||||||
isPricingByDayExperiment: null,
|
isPricingByDayExperiment: null,
|
||||||
pricingByDayBasePrice: null,
|
pricingByDayBasePrice: null,
|
||||||
pricingByDayUpcharge: null,
|
pricingByDayUpcharge: null,
|
||||||
showPricingByDay: null,
|
showPricingByDay: null,
|
||||||
selectableDatesInshop: [],
|
selectableDatesInshop: {},
|
||||||
selectableDatesMobile: [],
|
selectableDatesMobile: {},
|
||||||
preSelectedDate: null,
|
preSelectedDate: null,
|
||||||
streetAddress: this.getServiceAddressFromStore(),
|
streetAddress: this.getServiceAddressFromStore(),
|
||||||
apartmentNumberOrBusinessName: this.getServiceAddress2FromStore(),
|
apartmentNumberOrBusinessName: this.getServiceAddress2FromStore(),
|
||||||
|
|
@ -474,6 +481,7 @@ export default {
|
||||||
experimentSettings.SHOW_MOBILE_FIRST_APPT,
|
experimentSettings.SHOW_MOBILE_FIRST_APPT,
|
||||||
"true"
|
"true"
|
||||||
),
|
),
|
||||||
|
calendarLoadingStatus: "none",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async beforeRouteEnter(to, from, next) {
|
async beforeRouteEnter(to, from, next) {
|
||||||
|
|
@ -584,6 +592,8 @@ export default {
|
||||||
|
|
||||||
// Call the "next" function to complete the transition to this page.
|
// Call the "next" function to complete the transition to this page.
|
||||||
next((vm) => {
|
next((vm) => {
|
||||||
|
vm.showLoadingModal();
|
||||||
|
vm.calendarLoadingStatus = "initial";
|
||||||
vm.setCmsContent(resultMap.cmsContent);
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons);
|
vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons);
|
||||||
vm.mobilePremiumAppointmentFee = pricedPremiumFee;
|
vm.mobilePremiumAppointmentFee = pricedPremiumFee;
|
||||||
|
|
@ -604,6 +614,9 @@ export default {
|
||||||
);
|
);
|
||||||
vm.initializeDatePicker().then(() => {
|
vm.initializeDatePicker().then(() => {
|
||||||
vm.showMobileFirstModal();
|
vm.showMobileFirstModal();
|
||||||
|
vm.hideLoadingModal();
|
||||||
|
vm.calendarLoadingStatus = "none";
|
||||||
|
if (!vm.preSelectedDate) vm.setSelectedDateToFirstAvailable();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -620,8 +633,25 @@ export default {
|
||||||
this.handleZipCodeChange(newValue);
|
this.handleZipCodeChange(newValue);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
showShopLocationComponent() {
|
||||||
|
// If we get no inshop or mobile data, then show the shoplocation component so the user is not
|
||||||
|
// looking at a half blank screen with no way to try another zip/location.
|
||||||
|
if (
|
||||||
|
(this.selectableDatesInshop === undefined &&
|
||||||
|
this.selectableDatesMobile === undefined) ||
|
||||||
|
(this.selectableDatesInshop?.days?.length === 0 &&
|
||||||
|
this.selectableDatesMobile?.days?.length === 0) ||
|
||||||
|
(this.appointmentType && this.appointmentType !== AppointmentTypeStrings.MOBILE)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
isMobileSelected() {
|
isMobileSelected() {
|
||||||
return this.appointmentType == AppointmentTypeStrings.MOBILE;
|
return (
|
||||||
|
this.appointmentType == AppointmentTypeStrings.MOBILE ||
|
||||||
|
(this.isServiceableMobile && !this.isServiceableInshop)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
isServiceableMobile() {
|
isServiceableMobile() {
|
||||||
if (this.isRecalibrationServiceableMobile !== null) {
|
if (this.isRecalibrationServiceableMobile !== null) {
|
||||||
|
|
@ -633,15 +663,18 @@ export default {
|
||||||
return this.isGlassServiceableMobile;
|
return this.isGlassServiceableMobile;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
closestInshopProviderNumber() {
|
||||||
|
return this.shopProviderData?.shopProviders[0]?.providerNumber;
|
||||||
|
},
|
||||||
showTimeSlotQuestion() {
|
showTimeSlotQuestion() {
|
||||||
if (
|
if (
|
||||||
this.selectedDate &&
|
this.selectedDate &&
|
||||||
this.selectedDate.includes("mobile") &&
|
this.appointmentTypeFromAppointmentTypeQuestion &&
|
||||||
!this.isMobileSelected
|
this.timeSlotsForSelectedDate
|
||||||
) {
|
) {
|
||||||
this.setSelectedDateToFirstAvailable();
|
return true;
|
||||||
}
|
}
|
||||||
return this.selectedDate && this.appointmentTypeFromAppointmentTypeQuestion;
|
return false;
|
||||||
},
|
},
|
||||||
isMobileStaticRecalibrationApplicable() {
|
isMobileStaticRecalibrationApplicable() {
|
||||||
return (
|
return (
|
||||||
|
|
@ -869,6 +902,60 @@ export default {
|
||||||
? this.$refs.mobileFirstModal?.getIsModalOpen()
|
? this.$refs.mobileFirstModal?.getIsModalOpen()
|
||||||
: false;
|
: false;
|
||||||
},
|
},
|
||||||
|
displayWaitList() {
|
||||||
|
if (!this.appointmentType) return false;
|
||||||
|
const firstMobileDateString =
|
||||||
|
Array.isArray(this.selectableDatesMobile.days) &&
|
||||||
|
this.selectableDatesMobile.days.length > 0
|
||||||
|
? this.selectableDatesMobile.days[0].date
|
||||||
|
: null;
|
||||||
|
const firstInshopDateString =
|
||||||
|
Array.isArray(this.selectableDatesInshop.days) &&
|
||||||
|
this.selectableDatesInshop.days.length > 0
|
||||||
|
? this.selectableDatesInshop.days[0].date
|
||||||
|
: null;
|
||||||
|
const dateString =
|
||||||
|
this.isMobileSelected && firstMobileDateString
|
||||||
|
? firstMobileDateString
|
||||||
|
: firstInshopDateString;
|
||||||
|
|
||||||
|
if (
|
||||||
|
experimentMixin.methods.hasSettingEqualTo(
|
||||||
|
experimentSettings.DISPLAY_WAITLIST,
|
||||||
|
"true"
|
||||||
|
) &&
|
||||||
|
dateString
|
||||||
|
) {
|
||||||
|
const [year, month, day] = dateString.split("-").map(Number);
|
||||||
|
const targetDate = new Date(year, month - 1, day);
|
||||||
|
const currentDate = getTodayDate();
|
||||||
|
const futureDate = new Date(currentDate);
|
||||||
|
const experimentThresholdDays = experimentMixin.methods.hasSetting(
|
||||||
|
experimentSettings.WAITLIST_THRESHOLD_DAYS
|
||||||
|
)
|
||||||
|
? parseInt(
|
||||||
|
experimentMixin.methods.getSettingValue(
|
||||||
|
experimentSettings.WAITLIST_THRESHOLD_DAYS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: 0;
|
||||||
|
futureDate.setDate(currentDate.getDate() + experimentThresholdDays);
|
||||||
|
|
||||||
|
if (targetDate >= futureDate) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
hasSelectableDatesLoaded() {
|
||||||
|
if (this.isMobileSelected) {
|
||||||
|
return this.selectableDatesMobile?.days?.length > 0;
|
||||||
|
} else {
|
||||||
|
return this.selectableDatesInshop?.days?.length > 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
splitCopyOnCMSPlaceHolder,
|
splitCopyOnCMSPlaceHolder,
|
||||||
|
|
@ -1130,37 +1217,17 @@ export default {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getTimeSlotInfo() {
|
|
||||||
// Get the current date
|
|
||||||
const currentDate = getTodayDate();
|
|
||||||
|
|
||||||
// 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",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
onShopSelected(shopQuestionPopUpData) {
|
onShopSelected(shopQuestionPopUpData) {
|
||||||
this.resetWaitlist();
|
this.resetWaitlist();
|
||||||
this.preSelectedDate = null;
|
this.preSelectedDate = null;
|
||||||
|
this.calendarLoadingStatus = "reinitial";
|
||||||
this.shopProviderData = shopQuestionPopUpData.shopProviderData;
|
this.shopProviderData = shopQuestionPopUpData.shopProviderData;
|
||||||
const oldProvider = this.selectedProvider;
|
const oldProvider = this.selectedProvider;
|
||||||
const selectedProvider = this.shopProviderData.shopProviders.find((shopProvider) => {
|
const selectedProvider = this.shopProviderData.shopProviders.find((shopProvider) => {
|
||||||
return shopProvider.providerNumber === shopQuestionPopUpData.selectedProviderNumber;
|
return shopProvider.providerNumber === shopQuestionPopUpData.selectedProviderNumber;
|
||||||
});
|
});
|
||||||
|
const didProviderNumberChange =
|
||||||
|
oldProvider?.providerNumber !== selectedProvider?.providerNumber;
|
||||||
|
|
||||||
// No zipCodeData comes back if zip was not changed
|
// No zipCodeData comes back if zip was not changed
|
||||||
if (shopQuestionPopUpData.zipCodeData) {
|
if (shopQuestionPopUpData.zipCodeData) {
|
||||||
|
|
@ -1169,24 +1236,32 @@ export default {
|
||||||
selectedProvider
|
selectedProvider
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const didProviderNumberChange =
|
|
||||||
oldProvider?.providerNumber !== selectedProvider.providerNumber;
|
|
||||||
if (didProviderNumberChange) {
|
if (didProviderNumberChange) {
|
||||||
this.updateSelectedProvider(selectedProvider);
|
this.updateSelectedProvider(selectedProvider);
|
||||||
this.initializeDatePicker();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.initializeDatePicker().then(() => {
|
||||||
|
this.showMobileFirstModal();
|
||||||
|
this.hideLoadingModal();
|
||||||
|
this.calendarLoadingStatus = "none";
|
||||||
|
this.setSelectedDateToFirstAvailable();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
async getMoreScheduleData(startDate, endDate) {
|
async getMoreScheduleData(startDate, endDate) {
|
||||||
// called only when "View more dates" is clicked; not on initial page load
|
// called only when "View more dates" is clicked; not on initial page load
|
||||||
|
this.calendarLoadingStatus = "more";
|
||||||
const moreShopTimeSlots = await getScheduleApiResponse({
|
const moreShopTimeSlots = await getScheduleApiResponse({
|
||||||
startDateString: startDate,
|
startDateString: startDate,
|
||||||
endDateString: endDate,
|
endDateString: endDate,
|
||||||
providerNumber: this.selectedProvider?.providerNumber,
|
providerNumber: this.selectedProvider?.providerNumber,
|
||||||
|
inshopProviderNumber: this.isMobileSelected
|
||||||
|
? this.closestInshopProviderNumber
|
||||||
|
: this.selectedProvider?.providerNumber,
|
||||||
zipCode: this.zipCode,
|
zipCode: this.zipCode,
|
||||||
includeMobileTimeSlots: this.isServiceableMobile,
|
includeMobileTimeSlots: this.isServiceableMobile,
|
||||||
includeInshopTimeSlots: this.isServiceableInshop || this.isServiceableDropoff,
|
includeInshopTimeSlots: this.isServiceableInshop || this.isServiceableDropoff,
|
||||||
});
|
});
|
||||||
|
this.calendarLoadingStatus = "none";
|
||||||
|
|
||||||
// ADD API CALL RESULTS TO EXISTING DATE DATA
|
// ADD API CALL RESULTS TO EXISTING DATE DATA
|
||||||
this.selectableDatesInshop.days = this.selectableDatesInshop.days.concat(
|
this.selectableDatesInshop.days = this.selectableDatesInshop.days.concat(
|
||||||
|
|
@ -1202,8 +1277,6 @@ export default {
|
||||||
async initializeDatePicker() {
|
async initializeDatePicker() {
|
||||||
this.isShowMobileFirstAppt && this.showLoadingModal();
|
this.isShowMobileFirstAppt && this.showLoadingModal();
|
||||||
|
|
||||||
this.selectedDate = null;
|
|
||||||
|
|
||||||
const includeMobileTimeSlots = this.isServiceableMobile;
|
const includeMobileTimeSlots = this.isServiceableMobile;
|
||||||
const includeInshopTimeSlots = this.isServiceableInshop || this.isServiceableDropoff;
|
const includeInshopTimeSlots = this.isServiceableInshop || this.isServiceableDropoff;
|
||||||
const datePickerInitialData = await this.$refs.datePicker.loadInitialData({
|
const datePickerInitialData = await this.$refs.datePicker.loadInitialData({
|
||||||
|
|
@ -1213,6 +1286,9 @@ export default {
|
||||||
getSelectableDatesCallback: getScheduleApiResponse,
|
getSelectableDatesCallback: getScheduleApiResponse,
|
||||||
preSelectedDate: this.preSelectedDate,
|
preSelectedDate: this.preSelectedDate,
|
||||||
providerNumber: this.selectedProvider?.providerNumber,
|
providerNumber: this.selectedProvider?.providerNumber,
|
||||||
|
inshopProviderNumber: this.isMobileSelected
|
||||||
|
? this.closestInshopProviderNumber
|
||||||
|
: this.selectedProvider?.providerNumber,
|
||||||
zipCode: this.zipCode,
|
zipCode: this.zipCode,
|
||||||
includeMobileTimeSlots: includeMobileTimeSlots,
|
includeMobileTimeSlots: includeMobileTimeSlots,
|
||||||
includeInshopTimeSlots: includeInshopTimeSlots,
|
includeInshopTimeSlots: includeInshopTimeSlots,
|
||||||
|
|
@ -1224,7 +1300,6 @@ export default {
|
||||||
await datePickerInitialData.initialShopTimeSlotsResponse.inshopTimeSlotsData;
|
await datePickerInitialData.initialShopTimeSlotsResponse.inshopTimeSlotsData;
|
||||||
this.selectableDatesMobile =
|
this.selectableDatesMobile =
|
||||||
await datePickerInitialData.initialShopTimeSlotsResponse.mobileTimeSlotsData;
|
await datePickerInitialData.initialShopTimeSlotsResponse.mobileTimeSlotsData;
|
||||||
this.setDisplayWaitList();
|
|
||||||
|
|
||||||
if (this.preSelectedDate) {
|
if (this.preSelectedDate) {
|
||||||
this.selectedDate = this.preSelectedDate;
|
this.selectedDate = this.preSelectedDate;
|
||||||
|
|
@ -1236,11 +1311,11 @@ export default {
|
||||||
) {
|
) {
|
||||||
await this.$nextTick();
|
await this.$nextTick();
|
||||||
await this.$refs.datePicker.showAnotherMonth();
|
await this.$refs.datePicker.showAnotherMonth();
|
||||||
this.setDisplayWaitList();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
this.isShowMobileFirstAppt && this.hideLoadingModal();
|
getIsRecalAcknowledgedForSchedulingFromStore() {
|
||||||
|
return store.getters.order.isRecalAcknowledgedForScheduling;
|
||||||
},
|
},
|
||||||
getScheduleApiResponse,
|
getScheduleApiResponse,
|
||||||
getServiceZipCtuCodeFromStore() {
|
getServiceZipCtuCodeFromStore() {
|
||||||
|
|
@ -1251,8 +1326,8 @@ export default {
|
||||||
if (isMobileSelected === undefined) {
|
if (isMobileSelected === undefined) {
|
||||||
isMobileSelected = this.appointmentType === AppointmentTypeStrings.MOBILE;
|
isMobileSelected = this.appointmentType === AppointmentTypeStrings.MOBILE;
|
||||||
}
|
}
|
||||||
let storedDate = store.getters.order.schedule.date;
|
let storedDate = store.getters.order.schedule?.date;
|
||||||
if (isMobileSelected) storedDate += "-mobile";
|
if (isMobileSelected && storedDate) storedDate += "-mobile";
|
||||||
return storedDate;
|
return storedDate;
|
||||||
},
|
},
|
||||||
getSelectedTimeSlotInfo() {
|
getSelectedTimeSlotInfo() {
|
||||||
|
|
@ -1360,7 +1435,25 @@ export default {
|
||||||
hideLoadingModal() {
|
hideLoadingModal() {
|
||||||
this.$refs.loadingModal.hideModal();
|
this.$refs.loadingModal.hideModal();
|
||||||
},
|
},
|
||||||
|
async closeRecalAcknowledgementPopup() {
|
||||||
|
this.showRecalAcknowledgementPopup = false;
|
||||||
|
},
|
||||||
async forwardButtonAction() {
|
async forwardButtonAction() {
|
||||||
|
const showRecalAcknowledgementModal = this.showRecalAcknowledgementModal();
|
||||||
|
if (
|
||||||
|
showRecalAcknowledgementModal &&
|
||||||
|
this.isRecalAcknowledgedForScheduling !== RECAL_ACK_YES
|
||||||
|
) {
|
||||||
|
this.showRecalAcknowledgementPopup = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dispatchStoreAction(
|
||||||
|
storeActions.SAVE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING,
|
||||||
|
this.isRecalAcknowledgedForScheduling,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
this.navigatingForward = true;
|
this.navigatingForward = true;
|
||||||
var ctu = this.zipCodeCtu;
|
var ctu = this.zipCodeCtu;
|
||||||
if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||||
|
|
@ -1489,39 +1582,6 @@ export default {
|
||||||
}
|
}
|
||||||
this.showLoadingModal();
|
this.showLoadingModal();
|
||||||
},
|
},
|
||||||
setDisplayWaitList() {
|
|
||||||
const dateString = this.isMobileSelected
|
|
||||||
? this.selectableDatesMobile?.days[0]?.date
|
|
||||||
: this.selectableDatesInshop?.days[0]?.date;
|
|
||||||
if (
|
|
||||||
experimentMixin.methods.hasSettingEqualTo(
|
|
||||||
experimentSettings.DISPLAY_WAITLIST,
|
|
||||||
"true"
|
|
||||||
) &&
|
|
||||||
dateString
|
|
||||||
) {
|
|
||||||
const [year, month, day] = dateString.split("-").map(Number);
|
|
||||||
const targetDate = new Date(year, month - 1, day);
|
|
||||||
const currentDate = getTodayDate();
|
|
||||||
const futureDate = new Date(currentDate);
|
|
||||||
const experimentThresholdDays = experimentMixin.methods.hasSetting(
|
|
||||||
experimentSettings.WAITLIST_THRESHOLD_DAYS
|
|
||||||
)
|
|
||||||
? parseInt(
|
|
||||||
experimentMixin.methods.getSettingValue(
|
|
||||||
experimentSettings.WAITLIST_THRESHOLD_DAYS
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: 0;
|
|
||||||
futureDate.setDate(currentDate.getDate() + experimentThresholdDays);
|
|
||||||
|
|
||||||
if (targetDate >= futureDate) {
|
|
||||||
this.displayWaitList = true;
|
|
||||||
} else {
|
|
||||||
this.displayWaitList = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateSupportingItems() {
|
updateSupportingItems() {
|
||||||
const supportingItems = this.getSupportingItems();
|
const supportingItems = this.getSupportingItems();
|
||||||
|
|
||||||
|
|
@ -1591,28 +1651,39 @@ export default {
|
||||||
handleWaitListRequested(value) {
|
handleWaitListRequested(value) {
|
||||||
this.waitListRequested = value;
|
this.waitListRequested = value;
|
||||||
},
|
},
|
||||||
handleDateClicked(date) {
|
handleDateSelected(date) {
|
||||||
// do something to mark this as upcharge day or not...
|
const previousDate = this.selectedDate;
|
||||||
if (date.isPricingByDayUpchargeDay) {
|
// check if date actually changed
|
||||||
this.includePricingByDayUpcharge = true;
|
if (previousDate !== date.dateString) {
|
||||||
} else {
|
this.selectedTimeSlotInfo = this.getEmptyTimeSlot();
|
||||||
this.includePricingByDayUpcharge = false;
|
this.updateFooterButtonText(this.selectedTimeSlotInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: REMOVE AS PART OF CASH-1634
|
||||||
|
// // do something to mark this as upcharge day or not...
|
||||||
|
// if (date.isPricingByDayUpchargeDay) {
|
||||||
|
// this.includePricingByDayUpcharge = true;
|
||||||
|
// } else {
|
||||||
|
// this.includePricingByDayUpcharge = false;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
getEmptyTimeSlot() {
|
||||||
|
return {
|
||||||
|
timeSlot: {
|
||||||
|
date: null,
|
||||||
|
routeCode: null,
|
||||||
|
startTime: null,
|
||||||
|
endTime: null,
|
||||||
|
jobMaxMinutes: null,
|
||||||
|
jobMinMinutes: null,
|
||||||
|
},
|
||||||
|
isPremiumAppointment: null,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
updateTimeSlot(timeSlotObj) {
|
updateTimeSlot(timeSlotObj) {
|
||||||
if (!timeSlotObj?.routeCode) {
|
if (!timeSlotObj?.routeCode) {
|
||||||
this.appointmentType = AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF;
|
this.appointmentType = AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF;
|
||||||
this.selectedTimeSlotInfo = {
|
this.selectedTimeSlotInfo = this.getEmptyTimeSlot();
|
||||||
timeSlot: {
|
|
||||||
date: null,
|
|
||||||
routeCode: null,
|
|
||||||
startTime: null,
|
|
||||||
endTime: null,
|
|
||||||
jobMaxMinutes: null,
|
|
||||||
jobMinMinutes: null,
|
|
||||||
},
|
|
||||||
isPremiumAppointment: null,
|
|
||||||
};
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const timeSlot = this.timeSlotsForSelectedDate?.timeSlots?.find(
|
const timeSlot = this.timeSlotsForSelectedDate?.timeSlots?.find(
|
||||||
|
|
@ -1662,6 +1733,8 @@ export default {
|
||||||
this.updateSelectedProvider();
|
this.updateSelectedProvider();
|
||||||
},
|
},
|
||||||
updateSelectedProvider(newShopProvider) {
|
updateSelectedProvider(newShopProvider) {
|
||||||
|
if (!this.appointmentTypeStrings) return;
|
||||||
|
|
||||||
if (newShopProvider) {
|
if (newShopProvider) {
|
||||||
this.selectedProvider = newShopProvider;
|
this.selectedProvider = newShopProvider;
|
||||||
} else if (this.appointmentType === this.appointmentTypeStrings.MOBILE) {
|
} else if (this.appointmentType === this.appointmentTypeStrings.MOBILE) {
|
||||||
|
|
@ -1682,8 +1755,29 @@ export default {
|
||||||
this.selectedProvider = this.shopProviderData.shopProviders[0];
|
this.selectedProvider = this.shopProviderData.shopProviders[0];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleZipCodeChange(newZipCode) {
|
async handleZipCodeChange(newZipCode) {
|
||||||
|
// if we were showing the recal ack modal and now are changing the zip, we need to navigate
|
||||||
|
// to service-zip so we can get call parts again to add the recal part if needed
|
||||||
|
if (this.shouldShowRecalAckModal()) {
|
||||||
|
await baseMixin.methods.dispatchStoreAction(
|
||||||
|
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
|
||||||
|
{
|
||||||
|
zipCode: newZipCode.zipCode,
|
||||||
|
state: newZipCode.state,
|
||||||
|
zipCodeCtu: newZipCode.zipCodeCtu,
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
this.$router.navigateWithSaving(
|
||||||
|
this.navigationScenarios.ZIP_CODE_CHANGED_RECAL_ACK,
|
||||||
|
this.pageName
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.resetMobileLocation();
|
this.resetMobileLocation();
|
||||||
|
this.calendarLoadingStatus = "initial";
|
||||||
|
|
||||||
getShopProviderData(newZipCode.zipCode, this.pageName).then((result) => {
|
getShopProviderData(newZipCode.zipCode, this.pageName).then((result) => {
|
||||||
this.shopProviderData = result.data;
|
this.shopProviderData = result.data;
|
||||||
|
|
@ -1694,6 +1788,9 @@ export default {
|
||||||
this.selectedDate = null;
|
this.selectedDate = null;
|
||||||
this.initializeDatePicker().then(() => {
|
this.initializeDatePicker().then(() => {
|
||||||
this.showMobileFirstModal();
|
this.showMobileFirstModal();
|
||||||
|
this.hideLoadingModal();
|
||||||
|
this.calendarLoadingStatus = "none";
|
||||||
|
this.setSelectedDateToFirstAvailable();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -1705,10 +1802,12 @@ export default {
|
||||||
this.zipContainsMilitaryBase = newZipCode.containsMilitaryBase;
|
this.zipContainsMilitaryBase = newZipCode.containsMilitaryBase;
|
||||||
this.selectedDate = null;
|
this.selectedDate = null;
|
||||||
this.selectedProvider = newProvider;
|
this.selectedProvider = newProvider;
|
||||||
this.initializeDatePicker();
|
|
||||||
},
|
},
|
||||||
handleAppointmentTypeChange(newAppointmentType) {
|
handleAppointmentTypeChange(newAppointmentType) {
|
||||||
this.updateFooterButtonText();
|
this.updateFooterButtonText();
|
||||||
|
// if time appointment type changes, clear any selected time slot
|
||||||
|
this.selectedTimeSlotInfo = this.getEmptyTimeSlot();
|
||||||
|
|
||||||
if (newAppointmentType === AppointmentTypeStrings.MOBILE) {
|
if (newAppointmentType === AppointmentTypeStrings.MOBILE) {
|
||||||
// Remember last shop selected if previous selection was inshop/dropoff
|
// Remember last shop selected if previous selection was inshop/dropoff
|
||||||
if (
|
if (
|
||||||
|
|
@ -1717,17 +1816,6 @@ export default {
|
||||||
AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF) &&
|
AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF) &&
|
||||||
this.selectedProvider
|
this.selectedProvider
|
||||||
) {
|
) {
|
||||||
this.selectedTimeSlotInfo = {
|
|
||||||
timeSlot: {
|
|
||||||
date: null,
|
|
||||||
routeCode: null,
|
|
||||||
startTime: null,
|
|
||||||
endTime: null,
|
|
||||||
jobMaxMinutes: null,
|
|
||||||
jobMinMinutes: null,
|
|
||||||
},
|
|
||||||
isPremiumAppointment: null,
|
|
||||||
};
|
|
||||||
this.lastSelectedInshopOrDropoffProvider = this.selectedProvider;
|
this.lastSelectedInshopOrDropoffProvider = this.selectedProvider;
|
||||||
}
|
}
|
||||||
this.appointmentType = AppointmentTypeStrings.MOBILE;
|
this.appointmentType = AppointmentTypeStrings.MOBILE;
|
||||||
|
|
@ -1746,16 +1834,45 @@ export default {
|
||||||
this.appointmentType = AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF;
|
this.appointmentType = AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF;
|
||||||
}
|
}
|
||||||
// make sure a selectedProvider exists
|
// make sure a selectedProvider exists
|
||||||
this.updateSelectedProvider(this.lastSelectedInshopOrDropoffProvider);
|
if (this.lastSelectedInshopOrDropoffProvider) {
|
||||||
|
this.updateSelectedProvider(this.lastSelectedInshopOrDropoffProvider);
|
||||||
|
} else {
|
||||||
|
this.updateSelectedProvider();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.appointmentType = null;
|
this.appointmentType = null;
|
||||||
}
|
}
|
||||||
this.setSelectedDateToFirstAvailable();
|
this.setSelectedDateToFirstAvailable(); // v-if on appointmentTypeQuestion ensures dates have been loaded by now
|
||||||
this.setDisplayWaitList();
|
|
||||||
},
|
},
|
||||||
setSelectedDateToFirstAvailable() {
|
setSelectedDateToFirstAvailable() {
|
||||||
this.selectedDate = this.getFirstAvailableDate();
|
this.selectedDate = this.getFirstAvailableDate();
|
||||||
},
|
},
|
||||||
|
showRecalAcknowledgementModal() {
|
||||||
|
if (this.shouldShowRecalAckModal()) {
|
||||||
|
this.$refs.navbar.removeLoader();
|
||||||
|
this.$refs.recalAckModal.openModal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.closeRecalAcknowledgementPopup();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
shouldShowRecalAckModal() {
|
||||||
|
// if any of the glass parts contain an item with the requiresRecalibration flag set to true
|
||||||
|
// and the order does not contain a recalibration part, show the recalibration acknowledgement modal
|
||||||
|
const order = this.$store.getters.order;
|
||||||
|
const containsRecalPart = containsRecalParts(order.lineItems);
|
||||||
|
const hasPartWithRecalRequirement = anyPartWithRequiresRecalFlag(order.lineItems);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!containsRecalPart &&
|
||||||
|
hasPartWithRecalRequirement &&
|
||||||
|
this.isRecalAcknowledgedForScheduling !== RECAL_ACK_YES
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
async showMobileFirstModal() {
|
async showMobileFirstModal() {
|
||||||
if (!this.selectedRouteCodeData?.routeCode) {
|
if (!this.selectedRouteCodeData?.routeCode) {
|
||||||
const pmMobileDays = experimentMixin.methods.getSettingValue(
|
const pmMobileDays = experimentMixin.methods.getSettingValue(
|
||||||
|
|
@ -1767,6 +1884,20 @@ export default {
|
||||||
const noMobileAvailableDays = experimentMixin.methods.getSettingValue(
|
const noMobileAvailableDays = experimentMixin.methods.getSettingValue(
|
||||||
experimentSettings.SHOW_NO_MOBILE_AVAILABLE_DAYS
|
experimentSettings.SHOW_NO_MOBILE_AVAILABLE_DAYS
|
||||||
);
|
);
|
||||||
|
const mobileFirstPopupRepairSetting = experimentMixin.methods.getSettingValue(
|
||||||
|
experimentSettings.SHOW_MOBILE_FIRST_POPUP_REPAIR
|
||||||
|
);
|
||||||
|
const mobileFirstPopupReplaceWithoutMsrSetting =
|
||||||
|
experimentMixin.methods.getSettingValue(
|
||||||
|
experimentSettings.SHOW_MOBILE_FIRST_POPUP_REPLACE_WITHOUT_MSR
|
||||||
|
);
|
||||||
|
const mobileFirstPopupReplaceWithMsrSetting =
|
||||||
|
experimentMixin.methods.getSettingValue(
|
||||||
|
experimentSettings.SHOW_MOBILE_FIRST_POPUP_REPLACE_WITH_MSR
|
||||||
|
);
|
||||||
|
|
||||||
|
const order = this.$store.getters.order;
|
||||||
|
const isRepair = order.damage.isRepair;
|
||||||
|
|
||||||
let preSelectedMobileAppointment = null;
|
let preSelectedMobileAppointment = null;
|
||||||
const todaysDate = getTodayDate();
|
const todaysDate = getTodayDate();
|
||||||
|
|
@ -1783,12 +1914,63 @@ export default {
|
||||||
(new Date(firstMobilePMDate) - todaysDate) / (1000 * 60 * 60 * 24);
|
(new Date(firstMobilePMDate) - todaysDate) / (1000 * 60 * 60 * 24);
|
||||||
|
|
||||||
const shouldExposeMobileFirstAppointment = () => {
|
const shouldExposeMobileFirstAppointment = () => {
|
||||||
return (
|
const mobileFirstDaysCheck =
|
||||||
(firstMobileAMDate &&
|
(firstMobileAMDate &&
|
||||||
numberOfDaysToFirstMobileAMDate <= noMobileAvailableDays) ||
|
numberOfDaysToFirstMobileAMDate <= noMobileAvailableDays) ||
|
||||||
(firstMobilePMDate &&
|
(firstMobilePMDate &&
|
||||||
numberOfDaysToFirstMobilePMDate <= noMobileAvailableDays)
|
numberOfDaysToFirstMobilePMDate <= noMobileAvailableDays);
|
||||||
);
|
debugLog("::: Mobile First Days Check:", mobileFirstDaysCheck);
|
||||||
|
|
||||||
|
if (
|
||||||
|
mobileFirstPopupRepairSetting === "false" &&
|
||||||
|
mobileFirstPopupReplaceWithoutMsrSetting === "false" &&
|
||||||
|
mobileFirstPopupReplaceWithMsrSetting === "false"
|
||||||
|
) {
|
||||||
|
debugLog(
|
||||||
|
"::: Mobile First Damage Popup settings are all false, using default check."
|
||||||
|
);
|
||||||
|
return mobileFirstDaysCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRepair) {
|
||||||
|
if (mobileFirstPopupRepairSetting === "true") {
|
||||||
|
debugLog(
|
||||||
|
"::: Mobile First Popup Repair setting is true, showing mobile first appointment for repair."
|
||||||
|
);
|
||||||
|
return mobileFirstDaysCheck;
|
||||||
|
} else {
|
||||||
|
debugLog(
|
||||||
|
"::: Mobile First Popup Repair setting is false, not showing mobile first appointment for repair."
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isMobileStaticRecalibrationApplicable) {
|
||||||
|
if (mobileFirstPopupReplaceWithMsrSetting === "true") {
|
||||||
|
debugLog(
|
||||||
|
"::: Mobile First Popup Replace with MSR setting is true, showing mobile first appointment for replace with MSR."
|
||||||
|
);
|
||||||
|
return mobileFirstDaysCheck;
|
||||||
|
} else {
|
||||||
|
debugLog(
|
||||||
|
"::: Mobile First Popup Replace with MSR setting is false, not showing mobile first appointment for replace with MSR."
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mobileFirstPopupReplaceWithoutMsrSetting === "true") {
|
||||||
|
debugLog(
|
||||||
|
"::: Mobile First Popup Replace without MSR setting is true, showing mobile first appointment for replace without MSR."
|
||||||
|
);
|
||||||
|
return mobileFirstDaysCheck;
|
||||||
|
} else {
|
||||||
|
debugLog(
|
||||||
|
"::: Mobile First Popup Replace without MSR setting is false, not showing mobile first appointment for replace without MSR."
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (shouldExposeMobileFirstAppointment()) {
|
if (shouldExposeMobileFirstAppointment()) {
|
||||||
|
|
@ -1813,23 +1995,34 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isShowMobileFirstAppt) {
|
if (this.isShowMobileFirstAppt) {
|
||||||
if (firstMobilePMDate && numberOfDaysToFirstMobilePMDate <= pmMobileDays) {
|
if (pmMobileDays == 0) {
|
||||||
|
// Selects the first available time slot
|
||||||
|
preSelectedMobileAppointment =
|
||||||
|
numberOfDaysToFirstMobileAMDate <= numberOfDaysToFirstMobilePMDate
|
||||||
|
? firstMobileAMAppt
|
||||||
|
: firstMobilePMAppt;
|
||||||
|
} else if (
|
||||||
|
firstMobilePMDate &&
|
||||||
|
numberOfDaysToFirstMobilePMDate <= pmMobileDays
|
||||||
|
) {
|
||||||
|
// Selects the first available PM time slot
|
||||||
preSelectedMobileAppointment = firstMobilePMAppt;
|
preSelectedMobileAppointment = firstMobilePMAppt;
|
||||||
} else if (
|
} else if (
|
||||||
firstMobilePMDate &&
|
firstMobilePMDate &&
|
||||||
numberOfDaysToFirstMobilePMDate >= noPMMobileDays
|
numberOfDaysToFirstMobilePMDate >= noPMMobileDays
|
||||||
) {
|
) {
|
||||||
|
// Selects the first available AM time slot. If none, selects the first available PM time slot
|
||||||
preSelectedMobileAppointment = firstMobileAMAppt
|
preSelectedMobileAppointment = firstMobileAMAppt
|
||||||
? firstMobileAMAppt
|
? firstMobileAMAppt
|
||||||
: null;
|
: firstMobilePMAppt;
|
||||||
!preSelectedMobileAppointment &&
|
|
||||||
(preSelectedMobileAppointment = firstMobilePMAppt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$refs.mobileFirstModal.setSelectedAppointment(
|
if (preSelectedMobileAppointment) {
|
||||||
preSelectedMobileAppointment
|
this.$refs.mobileFirstModal.setSelectedAppointment(
|
||||||
);
|
preSelectedMobileAppointment
|
||||||
this.$refs.mobileFirstModal.openModal();
|
);
|
||||||
|
this.$refs.mobileFirstModal.openModal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1868,39 +2061,17 @@ export default {
|
||||||
this.updateTimeSlot(timeSlotObj);
|
this.updateTimeSlot(timeSlotObj);
|
||||||
this.forwardButtonAction();
|
this.forwardButtonAction();
|
||||||
},
|
},
|
||||||
|
updateRecalAcknowledgedAndNavigateForward(isAcknowledged) {
|
||||||
|
this.isRecalAcknowledgedForScheduling = isAcknowledged;
|
||||||
|
this.forwardButtonAction();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
appointmentTypeFromAppointmentTypeQuestion: {
|
appointmentTypeFromAppointmentTypeQuestion: {
|
||||||
handler(newValue, oldValue) {
|
handler(newValue, oldValue) {
|
||||||
this.handleAppointmentTypeChange(newValue);
|
this.handleAppointmentTypeChange(newValue); // v-if on appointmentTypeQuestion ensures dates have been loaded by now
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
selectedDate(newValue, oldValue) {
|
|
||||||
// Clear time slot selection if date selected changes
|
|
||||||
const selectedDate = this.getSelectedDateFromStore();
|
|
||||||
if (oldValue && this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
|
||||||
oldValue = `${oldValue}-mobile`;
|
|
||||||
}
|
|
||||||
const hasValueChanged = newValue !== oldValue;
|
|
||||||
const isDateDifferent = (newValue || oldValue) !== selectedDate;
|
|
||||||
|
|
||||||
if (hasValueChanged && isDateDifferent && !this.selectedMobileFirstAppointment) {
|
|
||||||
this.selectedTimeSlotInfo = {
|
|
||||||
timeSlot: {
|
|
||||||
date: null,
|
|
||||||
routeCode: null,
|
|
||||||
startTime: null,
|
|
||||||
endTime: null,
|
|
||||||
jobMaxMinutes: null,
|
|
||||||
jobMinMinutes: null,
|
|
||||||
},
|
|
||||||
isPremiumAppointment: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectedTimeSlotInfo(newValue) {
|
|
||||||
this.updateFooterButtonText(newValue);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
funnelHeader,
|
funnelHeader,
|
||||||
|
|
@ -1913,7 +2084,7 @@ export default {
|
||||||
textBlock,
|
textBlock,
|
||||||
timeSlotQuestion,
|
timeSlotQuestion,
|
||||||
mobileFirstModal,
|
mobileFirstModal,
|
||||||
|
recalAckModal,
|
||||||
alert,
|
alert,
|
||||||
serviceZipModalQuestion,
|
serviceZipModalQuestion,
|
||||||
appointmentTypeQuestion,
|
appointmentTypeQuestion,
|
||||||
|
|
|
||||||
|
|
@ -548,9 +548,6 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
unmounted() {
|
|
||||||
if (this.isModalOpened) this.closeModal();
|
|
||||||
},
|
|
||||||
watch: {
|
watch: {
|
||||||
waitListRequested(newVal) {
|
waitListRequested(newVal) {
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="time-slots-question">
|
<div class="time-slots-question">
|
||||||
<buttonQuestion
|
<buttonQuestion
|
||||||
|
ref="chooseDropOffOrInshop"
|
||||||
v-if="isDropOffAppointmentAvailable"
|
v-if="isDropOffAppointmentAvailable"
|
||||||
v-model="selectedAnswerForDropOffOrInshop"
|
v-model="selectedAnswerForDropOffOrInshop"
|
||||||
@update:modelValue="dropOffSelectionChanged"
|
@update:modelValue="dropOffSelectionChanged"
|
||||||
|
|
@ -23,8 +24,9 @@
|
||||||
</div>
|
</div>
|
||||||
</buttonQuestion>
|
</buttonQuestion>
|
||||||
<buttonQuestion
|
<buttonQuestion
|
||||||
ref="buttonQuestion"
|
ref="chooseTimeSlot"
|
||||||
v-if="shouldDisplayTimeSlotQuestion"
|
v-if="displayTimeSlotQuestion"
|
||||||
|
v-model="selectedAnswerForTimeSlots"
|
||||||
@update:modelValue="timeSlotSelectionChanged"
|
@update:modelValue="timeSlotSelectionChanged"
|
||||||
buttonTypeString="timeSlotModalListButton"
|
buttonTypeString="timeSlotModalListButton"
|
||||||
:buttonTypeObject="timeSlotModalListButton"
|
:buttonTypeObject="timeSlotModalListButton"
|
||||||
|
|
@ -34,10 +36,12 @@
|
||||||
isInshopOrDropOffAppointment ? 'is-inshop-or-drop-off-appointment' : '',
|
isInshopOrDropOffAppointment ? 'is-inshop-or-drop-off-appointment' : '',
|
||||||
]"
|
]"
|
||||||
:answers="availableTimeSlots"
|
:answers="availableTimeSlots"
|
||||||
:lazyLoad="{ isLazyLoad: !isMobileAppointment, amountToLoad: 10 }"
|
:lazyLoad="{
|
||||||
|
isLazyLoad: isLazyLoad,
|
||||||
|
amountToLoad: lazyLoadAmountToLoad || 10,
|
||||||
|
}"
|
||||||
groupName="chooseTimeSlot"
|
groupName="chooseTimeSlot"
|
||||||
textPosition="text-center"
|
textPosition="text-center"
|
||||||
v-model="selectedAnswerForTimeSlots"
|
|
||||||
questionText="Available times:"
|
questionText="Available times:"
|
||||||
isRequired
|
isRequired
|
||||||
validationRules="time-slot-required" />
|
validationRules="time-slot-required" />
|
||||||
|
|
@ -133,6 +137,7 @@ export default {
|
||||||
isSameDay: Boolean,
|
isSameDay: Boolean,
|
||||||
displayWaitList: Boolean,
|
displayWaitList: Boolean,
|
||||||
selectedRouteCodeData: Object,
|
selectedRouteCodeData: Object,
|
||||||
|
lazyLoadAmountToLoad: Number,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -389,12 +394,32 @@ export default {
|
||||||
this.answersForDropOffQuestion[0].value !== PICK_A_TIME_BUTTON_VALUE
|
this.answersForDropOffQuestion[0].value !== PICK_A_TIME_BUTTON_VALUE
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
shouldDisplayTimeSlotQuestion() {
|
displayTimeSlotQuestion() {
|
||||||
return (
|
return (
|
||||||
this.selectedAnswerForDropOffOrInshop == PICK_A_TIME_BUTTON_VALUE ||
|
this.selectedAnswerForDropOffOrInshop == PICK_A_TIME_BUTTON_VALUE ||
|
||||||
!this.isDropOffAppointmentAvailable
|
!this.isDropOffAppointmentAvailable
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
isLazyLoad() {
|
||||||
|
if (this.availableTimeSlots?.length < this.lazyLoadAmountToLoad + 1) return false;
|
||||||
|
if (this.isMobileAppointment) return false;
|
||||||
|
if (this.selectedAnswerForTimeSlots && !this.isSelectedInshopTimeSlotShowing)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
availableTimeSlotsForInshop() {
|
||||||
|
return this.getAvailableTimeSlotsForInshop(this.timeSlotsForSelectedDate?.timeSlots);
|
||||||
|
},
|
||||||
|
isSelectedInshopTimeSlotShowing() {
|
||||||
|
if (!this.selectedAnswerForTimeSlots) return false;
|
||||||
|
const idx = this.availableTimeSlotsForInshop?.findIndex((timeSlot) => {
|
||||||
|
return timeSlot.value === this.selectedAnswerForTimeSlots;
|
||||||
|
});
|
||||||
|
if (idx > -1 && idx < this.lazyLoadAmountToLoad) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateDropoffAndTimeSlotAnswersFromSelectedRouteCodeData(selectedRouteCodeData) {
|
updateDropoffAndTimeSlotAnswersFromSelectedRouteCodeData(selectedRouteCodeData) {
|
||||||
|
|
@ -452,6 +477,7 @@ export default {
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
getAvailableTimeSlotsForInshop(timeSlotsForSelectedDate) {
|
getAvailableTimeSlotsForInshop(timeSlotsForSelectedDate) {
|
||||||
|
if (!timeSlotsForSelectedDate) return;
|
||||||
return timeSlotsForSelectedDate
|
return timeSlotsForSelectedDate
|
||||||
.filter((timeSlot) => {
|
.filter((timeSlot) => {
|
||||||
return !isDropOffRouteCode(timeSlot.id);
|
return !isDropOffRouteCode(timeSlot.id);
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ export default {
|
||||||
},
|
},
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
this.$emit("update:modelValue", newValue);
|
this.$emit("update:modelValue", newValue);
|
||||||
|
this.$emit("handle-appointment-type-change", newValue);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
isMobileOnly() {
|
isMobileOnly() {
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,7 @@ import { errorMessages } from "@/constants/error-messages";
|
||||||
import { damageLocationsCms } from "@/constants/damage-locations-cms.js";
|
import { damageLocationsCms } from "@/constants/damage-locations-cms.js";
|
||||||
import { damageLocationsSelected } from "@/constants/damage-locations-selected.js";
|
import { damageLocationsSelected } from "@/constants/damage-locations-selected.js";
|
||||||
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
|
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
|
||||||
|
import { debugLog } from "@/helpers/debug-log-helper";
|
||||||
|
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
import baseMixin from "@/mixins/base-mixin";
|
import baseMixin from "@/mixins/base-mixin";
|
||||||
|
|
@ -144,6 +145,7 @@ export default {
|
||||||
if (store.getters.externalParameterState?.isExternalParameter) {
|
if (store.getters.externalParameterState?.isExternalParameter) {
|
||||||
await nextTick();
|
await nextTick();
|
||||||
const isValid = await baseMixin.methods.isFormValid(vm.$refs.theForm);
|
const isValid = await baseMixin.methods.isFormValid(vm.$refs.theForm);
|
||||||
|
debugLog(`**** vehicle-damage is form valid: ${isValid} ****`);
|
||||||
if (
|
if (
|
||||||
store.getters.vehicle?.vehicleSubType === "MOTOR HOME" &&
|
store.getters.vehicle?.vehicleSubType === "MOTOR HOME" &&
|
||||||
store.getters.externalParameterDamage.damageType?.toUpperCase() ===
|
store.getters.externalParameterDamage.damageType?.toUpperCase() ===
|
||||||
|
|
@ -183,7 +185,10 @@ export default {
|
||||||
const selectedGlassToReplace =
|
const selectedGlassToReplace =
|
||||||
resultMap.damageOptions.windshieldOptions.availableReplacementOptions;
|
resultMap.damageOptions.windshieldOptions.availableReplacementOptions;
|
||||||
|
|
||||||
if (selectedGlassToReplace == damageLocationsSelected.SINGLE) {
|
if (
|
||||||
|
selectedGlassToReplace == damageLocationsSelected.SINGLE &&
|
||||||
|
store.getters.externalParameterDamage.damageType
|
||||||
|
) {
|
||||||
vm.selectedWindshieldOptions.selectedWindshieldReplaceOptions.push(
|
vm.selectedWindshieldOptions.selectedWindshieldReplaceOptions.push(
|
||||||
damageLocationsSelected.SINGLE
|
damageLocationsSelected.SINGLE
|
||||||
);
|
);
|
||||||
|
|
@ -251,6 +256,8 @@ export default {
|
||||||
if (store.getters.vehicle.carId) {
|
if (store.getters.vehicle.carId) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
debugLog("**** Vehicle Page Prereq invalid ****");
|
||||||
|
debugLog("store.getters.vehicle.carId:", store.getters.vehicle?.carId, true);
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
attachCustomEvents() {
|
attachCustomEvents() {
|
||||||
|
|
|
||||||
|
|
@ -198,6 +198,8 @@ export default {
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.dispatchStoreAction(this.storeActions.SAVE_IS_OEM_GLASS_SELECTED, false, false);
|
||||||
|
|
||||||
// Navigate to the next page
|
// Navigate to the next page
|
||||||
this.navigateForward(matchedParts);
|
this.navigateForward(matchedParts);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -95,9 +95,8 @@ describe("vin-lookup.vue", () => {
|
||||||
it("Should call navigateForward() if the store carId does not match the vin response carId but does match previously enterted carId and forward button is clicked", async () => {
|
it("Should call navigateForward() if the store carId does not match the vin response carId but does match previously enterted carId and forward button is clicked", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
mockOutPromises({ carId: "C11111" });
|
mockOutPromises({ carId: "C11111" }); //this line in above tests affects this test, so adding to here too till we figure out how to isolate the calls
|
||||||
|
|
||||||
wrapper.vm.lookupVehicle = jest.fn().mockImplementation(() => vinPromise);
|
|
||||||
wrapper.vm.navigateForward = jest.fn();
|
wrapper.vm.navigateForward = jest.fn();
|
||||||
wrapper.vm.previouslyEnteredCarId = "C11111";
|
wrapper.vm.previouslyEnteredCarId = "C11111";
|
||||||
|
|
||||||
|
|
@ -111,6 +110,7 @@ describe("vin-lookup.vue", () => {
|
||||||
it("Should not call navigateForward() if zip service returns a non-serviceable flag", async () => {
|
it("Should not call navigateForward() if zip service returns a non-serviceable flag", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
mockOutPromises({ carId: "C11111" }); //this line in above tests affects this test, so adding to here too till we figure out how to isolate the calls
|
||||||
const zipValidationApiResponse = {
|
const zipValidationApiResponse = {
|
||||||
data: {
|
data: {
|
||||||
isServiceable: false,
|
isServiceable: false,
|
||||||
|
|
@ -135,6 +135,8 @@ describe("vin-lookup.vue", () => {
|
||||||
it("Should not call navigateForward() when forward button is clicked but lookupVehicle errors out.", async () => {
|
it("Should not call navigateForward() when forward button is clicked but lookupVehicle errors out.", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
mockOutPromises({ carId: "C11111" }); //this line in above tests affects this test, so adding to here too till we figure out how to isolate the calls
|
||||||
|
|
||||||
wrapper.vm.vinTouched = true;
|
wrapper.vm.vinTouched = true;
|
||||||
wrapper.vm.vin = "foo";
|
wrapper.vm.vin = "foo";
|
||||||
wrapper.vm.initialVin = "!foo";
|
wrapper.vm.initialVin = "!foo";
|
||||||
|
|
@ -164,6 +166,7 @@ describe("vin-lookup.vue", () => {
|
||||||
isCarIdDifferent: true,
|
isCarIdDifferent: true,
|
||||||
isSelectedGlassAvailableForVehicle: false,
|
isSelectedGlassAvailableForVehicle: false,
|
||||||
});
|
});
|
||||||
|
wrapper.vm.pageName = "vin-lookup";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await wrapper.vm.navigateForward();
|
await wrapper.vm.navigateForward();
|
||||||
|
|
@ -172,9 +175,7 @@ describe("vin-lookup.vue", () => {
|
||||||
expect(wrapper.vm.$router.navigateWithSaving).toBeCalledTimes(1);
|
expect(wrapper.vm.$router.navigateWithSaving).toBeCalledTimes(1);
|
||||||
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
|
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
|
||||||
navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
|
navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
|
||||||
wrapper.vm.$route,
|
"vin-lookup"
|
||||||
expect.anything(),
|
|
||||||
expect.anything()
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -251,12 +252,14 @@ describe("vin-lookup.vue", () => {
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const vinLookup = wrapper.findComponent('[data-test="vin-lookup-component"]');
|
const vinLookup = wrapper.findComponent({ ref: "vinLookupQuestion" });
|
||||||
vinLookup.trigger("imageLookupError");
|
vinLookup.trigger("image-lookup-error");
|
||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(wrapper.findAllComponents({ name: "alert" }).length).toBe(1);
|
expect(wrapper.vm.displayVinScanFailedAlert).toBe(true);
|
||||||
|
//TODO - Fix original check for alert box but should show if displayVinScanFailedAlert is true which I'm checking
|
||||||
|
//expect(wrapper.findAllComponents({ cmsWidgetName: "AlertVinScanFailed" }).length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should hide the AlertNoService when displayNoServiceAlert is false", async () => {
|
test("should hide the AlertNoService when displayNoServiceAlert is false", async () => {
|
||||||
|
|
@ -277,6 +280,7 @@ describe("vin-lookup.vue", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
describe("getVinFromImage", () => {
|
describe("getVinFromImage", () => {
|
||||||
test("GetVinFromImage resolves with first valid VIN when any vins are returned.", async () => {
|
test("GetVinFromImage resolves with first valid VIN when any vins are returned.", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -289,7 +293,7 @@ describe("vin-lookup.vue", () => {
|
||||||
|
|
||||||
const storeMixin = {
|
const storeMixin = {
|
||||||
methods: {
|
methods: {
|
||||||
dispatchStoreAction: lookup,
|
dispatchStoreActionWithLogging: lookup,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -354,6 +358,7 @@ describe("vin-lookup.vue", () => {
|
||||||
await expect(promise).rejects.toEqual("An error occurred during the lookup.");
|
await expect(promise).rejects.toEqual("An error occurred during the lookup.");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
});
|
});
|
||||||
|
|
||||||
function setupMocks({ customMountOptions }) {
|
function setupMocks({ customMountOptions }) {
|
||||||
|
|
|
||||||
|
|
@ -393,21 +393,18 @@ export default {
|
||||||
|
|
||||||
// Check if Service Zip entered is serviceable then save the ZIP info
|
// Check if Service Zip entered is serviceable then save the ZIP info
|
||||||
if (zipCodeData.isServiceable) {
|
if (zipCodeData.isServiceable) {
|
||||||
//Only save the zipCode, state, and zipCodeCtu if the zip changed or we lack zipCodeCtu
|
//Always save the service zip info even if it was not changed;
|
||||||
if (
|
// if it didn't change it doesn't alter other values and this makes it more consistent
|
||||||
this.$store.getters.order.serviceLocation.zipCode != this.serviceZipCode ||
|
await this.dispatchStoreAction(
|
||||||
!this.$store.getters.order.serviceLocation.zipCodeCtu
|
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
|
||||||
) {
|
{
|
||||||
await this.dispatchStoreAction(
|
state: zipCodeData.state,
|
||||||
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
|
zipCode: this.serviceZipCode,
|
||||||
{
|
zipCodeCtu: zipCodeData.zipCodeCtu,
|
||||||
state: zipCodeData.state,
|
},
|
||||||
zipCode: this.serviceZipCode,
|
false
|
||||||
zipCodeCtu: zipCodeData.zipCodeCtu,
|
);
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// if no value due to field being optional, blank both phone and email address
|
// if no value due to field being optional, blank both phone and email address
|
||||||
if (!this.emailOrSms) {
|
if (!this.emailOrSms) {
|
||||||
await this.dispatchStoreAction(storeActions.SAVE_PHONE_NUMBER, "", false);
|
await this.dispatchStoreAction(storeActions.SAVE_PHONE_NUMBER, "", false);
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ import { applicationConfig } from "../constants/application-config";
|
||||||
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
|
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
|
||||||
import { routeData } from "@/router/constants/routes";
|
import { routeData } from "@/router/constants/routes";
|
||||||
import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
||||||
import { Variables } from "../constants/analytics";
|
|
||||||
import { containsRecalParts, getRecalPartNumbers } from "@/helpers/recal-helper";
|
import { containsRecalParts, getRecalPartNumbers } from "@/helpers/recal-helper";
|
||||||
import { getAmountDue, getSubTotal, getSalesTax } from "@/helpers/pricing-helper.js";
|
import { getAmountDue, getSubTotal, getSalesTax } from "@/helpers/pricing-helper.js";
|
||||||
import { partTypeStrings } from "@/constants/part-type-strings";
|
import { partTypeStrings } from "@/constants/part-type-strings";
|
||||||
|
|
@ -232,10 +231,41 @@ export default {
|
||||||
(part) => `${part.glassLocation}-${part.glassName}`
|
(part) => `${part.glassLocation}-${part.glassName}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const format = (part) => `${part?.partNumber}-${part?.partType ? part.partType : ""}`;
|
||||||
|
const mapParts = (arr) => (arr ?? []).map(format);
|
||||||
|
|
||||||
|
const glassParts = mapParts(order?.lineItems?.glassParts);
|
||||||
|
const supportingItems = mapParts(order?.lineItems?.supportingItems);
|
||||||
|
const vaps = mapParts(order?.lineItems?.vaps);
|
||||||
|
const childParts = [].concat(
|
||||||
|
...(order?.lineItems?.glassParts ?? []).map((gp) => mapParts(gp?.childParts))
|
||||||
|
);
|
||||||
|
const allParts = [...glassParts, ...supportingItems, ...vaps, ...childParts];
|
||||||
|
|
||||||
|
if (applicationUser?.pageData?.quote?.servicePackageSelected) {
|
||||||
|
allParts.push(applicationUser.pageData.quote.servicePackageSelected);
|
||||||
|
}
|
||||||
|
|
||||||
var appointment = `${order?.schedule?.date ?? ""} ${order?.schedule?.startTime ?? ""}`;
|
var appointment = `${order?.schedule?.date ?? ""} ${order?.schedule?.startTime ?? ""}`;
|
||||||
|
var currentPageName = getPageNameFromRouter();
|
||||||
|
|
||||||
|
// add query strings to the page name for debugging. on the vehicle page, if from an external link, pull it from the stash
|
||||||
|
if (currentPageName === "vehicle") {
|
||||||
|
if (!window.location.search) {
|
||||||
|
if (store.getters.externalParameterState?.qsStash) {
|
||||||
|
currentPageName += `${store.getters.externalParameterState.qsStash}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentPageName += `${window.location.search}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (window.location.search) {
|
||||||
|
currentPageName += `${window.location.search}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var sessionData = {};
|
var sessionData = {};
|
||||||
sessionData.currentPage = getPageNameFromRouter();
|
sessionData.currentPage = currentPageName;
|
||||||
sessionData.sid = getSessionIdValue();
|
sessionData.sid = getSessionIdValue();
|
||||||
sessionData.deviceId = getDeviceIdValue();
|
sessionData.deviceId = getDeviceIdValue();
|
||||||
sessionData.fmgSessionId = applicationUser?.savedSessionId;
|
sessionData.fmgSessionId = applicationUser?.savedSessionId;
|
||||||
|
|
@ -251,7 +281,7 @@ export default {
|
||||||
? "Insurance"
|
? "Insurance"
|
||||||
: "Cash";
|
: "Cash";
|
||||||
sessionData.damageType = order?.damage?.isRepair ? "Repair" : "Replace";
|
sessionData.damageType = order?.damage?.isRepair ? "Repair" : "Replace";
|
||||||
sessionData.productType = glassProducts;
|
sessionData.productType = allParts;
|
||||||
sessionData.eon = order?.eon;
|
sessionData.eon = order?.eon;
|
||||||
sessionData.referralNumber = order?.referralNumber;
|
sessionData.referralNumber = order?.referralNumber;
|
||||||
sessionData.referralSequenceNumber = order?.referralSequenceNumber;
|
sessionData.referralSequenceNumber = order?.referralSequenceNumber;
|
||||||
|
|
@ -937,9 +967,6 @@ export default {
|
||||||
ValueToLogTypes() {
|
ValueToLogTypes() {
|
||||||
return ValueToLogTypes;
|
return ValueToLogTypes;
|
||||||
},
|
},
|
||||||
Variables() {
|
|
||||||
return Variables;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ const navigationScenarios = {
|
||||||
|
|
||||||
// Schedule
|
// Schedule
|
||||||
CLICKED_CHANGE_LOCATION: "CLICKED_CHANGE_LOCATION",
|
CLICKED_CHANGE_LOCATION: "CLICKED_CHANGE_LOCATION",
|
||||||
|
ZIP_CODE_CHANGED_RECAL_ACK: "ZIP_CODE_CHANGED_RECAL_ACK",
|
||||||
|
|
||||||
// Payment
|
// Payment
|
||||||
CLICKED_INSURANCE: "CLICKED_INSURANCE",
|
CLICKED_INSURANCE: "CLICKED_INSURANCE",
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,10 @@ export const routeData = {
|
||||||
name: "quote",
|
name: "quote",
|
||||||
path: "/quote",
|
path: "/quote",
|
||||||
},
|
},
|
||||||
|
POLICY_INFO: {
|
||||||
|
name: "policy-info",
|
||||||
|
path: "/policy-info",
|
||||||
|
},
|
||||||
INSURANCE_COMPANY: {
|
INSURANCE_COMPANY: {
|
||||||
name: "insurance-company",
|
name: "insurance-company",
|
||||||
path: "/insurance-company",
|
path: "/insurance-company",
|
||||||
|
|
|
||||||
|
|
@ -429,6 +429,10 @@ const routingTable = function () {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
pageName: routeData.POLICY_INFO.name,
|
||||||
|
maps: [],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
pageName: routeData.INSURANCE_COMPANY.name,
|
pageName: routeData.INSURANCE_COMPANY.name,
|
||||||
maps: [
|
maps: [
|
||||||
|
|
@ -486,6 +490,10 @@ const routingTable = function () {
|
||||||
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOBILE_SERVICE,
|
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOBILE_SERVICE,
|
||||||
destinationPageData: routeData.MOBILE_DETAILS,
|
destinationPageData: routeData.MOBILE_DETAILS,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.ZIP_CODE_CHANGED_RECAL_ACK,
|
||||||
|
destinationPageData: routeData.SERVICE_ZIP,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -565,6 +573,10 @@ const routingTable = function () {
|
||||||
piaError: "true",
|
piaError: "true",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_PAY_LATER,
|
||||||
|
destinationPageData: routeData.CONFIRMATION,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -578,6 +590,17 @@ const routingTable = function () {
|
||||||
scenario: navigationScenarios.CLICKED_FORWARD,
|
scenario: navigationScenarios.CLICKED_FORWARD,
|
||||||
destinationPageData: routeData.CONFIRMATION,
|
destinationPageData: routeData.CONFIRMATION,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.PIA_ERROR,
|
||||||
|
destinationPageData: routeData.PAYMENT_METHOD,
|
||||||
|
params: {
|
||||||
|
piaError: "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_PAY_LATER,
|
||||||
|
destinationPageData: routeData.CONFIRMATION,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { storeActions } from "@/constants/store-actions";
|
||||||
import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
||||||
import { queryStrings } from "@/constants/query-strings";
|
import { queryStrings } from "@/constants/query-strings";
|
||||||
import { externalParameterStatus } from "@/constants/external-parameters";
|
import { externalParameterStatus } from "@/constants/external-parameters";
|
||||||
|
import { debugLog } from "@/helpers/debug-log-helper";
|
||||||
|
|
||||||
export async function initializeFromQueryStrings() {
|
export async function initializeFromQueryStrings() {
|
||||||
// Get advertiser info
|
// Get advertiser info
|
||||||
|
|
@ -26,12 +27,13 @@ function setupAdvertisersAndAffiliates() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateExternalParameterState() {
|
function updateExternalParameterState() {
|
||||||
|
debugLog("--- updateExternalParameterState ---");
|
||||||
const externalParameterYear = getQuerystringParameter(queryStrings.VEHICLE_YEAR);
|
const externalParameterYear = getQuerystringParameter(queryStrings.VEHICLE_YEAR);
|
||||||
const externalParameterMake = getQuerystringParameter(queryStrings.VEHICLE_MAKE);
|
const externalParameterMake = getQuerystringParameter(queryStrings.VEHICLE_MAKE);
|
||||||
const externalParameterModel = getQuerystringParameter(queryStrings.VEHICLE_MODEL);
|
const externalParameterModel = getQuerystringParameter(queryStrings.VEHICLE_MODEL);
|
||||||
const externalParameterStyle = getQuerystringParameter(queryStrings.VEHICLE_STYLE);
|
const externalParameterStyle = getQuerystringParameter(queryStrings.VEHICLE_STYLE);
|
||||||
const externalParameterDamage = getQuerystringParameter(queryStrings.VEHICLE_DAMAGE);
|
const externalParameterDamage = getQuerystringParameter(queryStrings.VEHICLE_DAMAGE);
|
||||||
const externalParameterZipCode = getQuerystringParameter(queryStrings.SERVICE_ZIP);
|
let externalParameterZipCode = getQuerystringParameter(queryStrings.SERVICE_ZIP);
|
||||||
const externalParameterEmail = getQuerystringParameter(queryStrings.EMAIL);
|
const externalParameterEmail = getQuerystringParameter(queryStrings.EMAIL);
|
||||||
const externalParameterIsInsurance = getQuerystringParameter(queryStrings.IS_INSURANCE);
|
const externalParameterIsInsurance = getQuerystringParameter(queryStrings.IS_INSURANCE);
|
||||||
const externalParameterVinSelection = getQuerystringParameter(queryStrings.VIN_SELECTION);
|
const externalParameterVinSelection = getQuerystringParameter(queryStrings.VIN_SELECTION);
|
||||||
|
|
@ -39,6 +41,27 @@ function updateExternalParameterState() {
|
||||||
const externalParameterNumberOfChips = getQuerystringParameter(queryStrings.NUMBER_OF_CHIPS);
|
const externalParameterNumberOfChips = getQuerystringParameter(queryStrings.NUMBER_OF_CHIPS);
|
||||||
const externalParameterSource = getQuerystringParameter(queryStrings.EXPERIMENTS);
|
const externalParameterSource = getQuerystringParameter(queryStrings.EXPERIMENTS);
|
||||||
const externalParameterPhoneNumber = getQuerystringParameter(queryStrings.PHONE_NUMBER);
|
const externalParameterPhoneNumber = getQuerystringParameter(queryStrings.PHONE_NUMBER);
|
||||||
|
|
||||||
|
if (!externalParameterZipCode) {
|
||||||
|
externalParameterZipCode = getQuerystringParameter(queryStrings.ZIP_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (externalParameterYear || externalParameterZipCode) {
|
||||||
|
debugLog("externalParameterYear:", externalParameterYear);
|
||||||
|
debugLog("externalParameterMake:", externalParameterMake);
|
||||||
|
debugLog("externalParameterModel:", externalParameterModel);
|
||||||
|
debugLog("externalParameterStyle:", externalParameterStyle);
|
||||||
|
debugLog("externalParameterDamage:", externalParameterDamage);
|
||||||
|
debugLog("externalParameterZipCode:", externalParameterZipCode);
|
||||||
|
debugLog("externalParameterEmail:", externalParameterEmail);
|
||||||
|
debugLog("externalParameterIsInsurance:", externalParameterIsInsurance);
|
||||||
|
debugLog("externalParameterVinSelection:", externalParameterVinSelection);
|
||||||
|
debugLog("externalParameterServicePackage:", externalParameterServicePackage);
|
||||||
|
debugLog("externalParameterNumberOfChips:", externalParameterNumberOfChips);
|
||||||
|
debugLog("externalParameterSource:", externalParameterSource);
|
||||||
|
debugLog("externalPhoneNumber:", externalParameterPhoneNumber);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
externalParameterYear &&
|
externalParameterYear &&
|
||||||
externalParameterMake &&
|
externalParameterMake &&
|
||||||
|
|
@ -51,6 +74,7 @@ function updateExternalParameterState() {
|
||||||
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_MAKE, externalParameterMake);
|
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_MAKE, externalParameterMake);
|
||||||
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_MODEL, externalParameterModel);
|
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_MODEL, externalParameterModel);
|
||||||
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_STYLE, externalParameterStyle);
|
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_STYLE, externalParameterStyle);
|
||||||
|
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_QS_STASH, window.location.search);
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
externalParameterDamage &&
|
externalParameterDamage &&
|
||||||
|
|
@ -100,6 +124,7 @@ function updateExternalParameterState() {
|
||||||
externalParameterPhoneNumber
|
externalParameterPhoneNumber
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
debugLog("--- updateExternalParameterState END ---");
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there is an existing external parameter state then they have already been through from an external source(LeadGen) and
|
// if there is an existing external parameter state then they have already been through from an external source(LeadGen) and
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,30 @@ export async function adyenReturnBeforeEnter(to, from) {
|
||||||
const redirectResult = to?.query?.redirectResult;
|
const redirectResult = to?.query?.redirectResult;
|
||||||
|
|
||||||
if (sessionId && redirectResult) {
|
if (sessionId && redirectResult) {
|
||||||
const result = await finalizeAdyenPayment(sessionId, redirectResult);
|
let result = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
result = await finalizeAdyenPayment(sessionId, redirectResult);
|
||||||
|
} catch (unexpectedResult) {
|
||||||
|
// If user cancelled payment, handle without error message
|
||||||
|
if (unexpectedResult.code === "Cancelled" || unexpectedResult.code === "CANCEL") {
|
||||||
|
return {
|
||||||
|
name: routeData.PAYMENT_ADYEN.name,
|
||||||
|
replace: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, failure scenario.
|
||||||
|
// Payment fails, so return user to payment-adyen screen to try again or pay later.
|
||||||
|
return {
|
||||||
|
name: routeData.PAYMENT_ADYEN.name,
|
||||||
|
query: {
|
||||||
|
piaFailure: true,
|
||||||
|
},
|
||||||
|
replace: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`Out of promise`);
|
console.log(`Out of promise`);
|
||||||
console.log(result);
|
console.log(result);
|
||||||
|
|
||||||
|
|
@ -29,6 +52,12 @@ export async function adyenReturnBeforeEnter(to, from) {
|
||||||
const amountDue = getAmountDue(store.getters.order.lineItems);
|
const amountDue = getAmountDue(store.getters.order.lineItems);
|
||||||
const ccToken = generateCcToken(sessionInfo);
|
const ccToken = generateCcToken(sessionInfo);
|
||||||
|
|
||||||
|
ccToken.authCode = "831001";
|
||||||
|
ccToken.cardType = "VS";
|
||||||
|
ccToken.lastFour = "1111";
|
||||||
|
ccToken.expMonth = "03";
|
||||||
|
ccToken.expYear = "2030";
|
||||||
|
|
||||||
await baseMixin.methods.dispatchStoreAction(
|
await baseMixin.methods.dispatchStoreAction(
|
||||||
storeActions.SAVE_PAYMENT_METHOD_CHOICE,
|
storeActions.SAVE_PAYMENT_METHOD_CHOICE,
|
||||||
paymentMethod,
|
paymentMethod,
|
||||||
|
|
@ -53,7 +82,10 @@ export async function adyenReturnBeforeEnter(to, from) {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return {
|
return {
|
||||||
name: routeData.ERROR.name,
|
name: routeData.PAYMENT_METHOD.name,
|
||||||
|
query: {
|
||||||
|
piaError: "true",
|
||||||
|
},
|
||||||
replace: true,
|
replace: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -87,12 +119,32 @@ function finalizeAdyenPayment(sessionId, redirectResult) {
|
||||||
onPaymentFailed: (result, component) => {
|
onPaymentFailed: (result, component) => {
|
||||||
console.log(`Payment failed`);
|
console.log(`Payment failed`);
|
||||||
console.log(result);
|
console.log(result);
|
||||||
reject("Payment failed from Adyen");
|
|
||||||
|
if (result?.resultCode !== "Cancelled") {
|
||||||
|
const stringToLog = `ADYEN PAYMENT NOT AUTHORIZED. Code = ${result?.resultCode}. Id = ${sessionId}`;
|
||||||
|
|
||||||
|
global.$logger.logError(stringToLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
reject({
|
||||||
|
code: result?.resultCode,
|
||||||
|
message: "Payment failed from Adyen",
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onError: (error, component) => {
|
onError: (error, component) => {
|
||||||
console.log(`Error occured`);
|
console.log(`Error occured`);
|
||||||
console.log(error);
|
console.log(error);
|
||||||
reject("Error from Adyen");
|
|
||||||
|
if (error?.name !== "CANCEL") {
|
||||||
|
const stringToLog = `ADYEN ERROR. Name = ${error?.name}. Details = ${error?.message}.`;
|
||||||
|
|
||||||
|
global.$logger.logError(stringToLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
reject({
|
||||||
|
code: error?.name,
|
||||||
|
message: "Error from Adyen",
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
options: {},
|
options: {},
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export async function vehicleBeforeEnter(to, from) {
|
||||||
if (zipData.isValid) {
|
if (zipData.isValid) {
|
||||||
store.dispatch(storeActions.SAVE_SERVICE_ZIP_CODE_INFO, {
|
store.dispatch(storeActions.SAVE_SERVICE_ZIP_CODE_INFO, {
|
||||||
zipCode: newZipFromQuerystring,
|
zipCode: newZipFromQuerystring,
|
||||||
state: zipData.state.state,
|
state: zipData.state,
|
||||||
zipCodeCtu: zipData.zipCodeCtu,
|
zipCodeCtu: zipData.zipCodeCtu,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ export const routes = [
|
||||||
createRoute(routeData.CONFIRMATION),
|
createRoute(routeData.CONFIRMATION),
|
||||||
createRoute(routeData.RETURN_USER),
|
createRoute(routeData.RETURN_USER),
|
||||||
createRoute(routeData.MOBILE_DETAILS),
|
createRoute(routeData.MOBILE_DETAILS),
|
||||||
|
createRoute(routeData.POLICY_INFO),
|
||||||
|
|
||||||
// Virtual pages (resolve to a non-virtual page.)
|
// Virtual pages (resolve to a non-virtual page.)
|
||||||
createVirtualRoute(routeData.LANDING, landingBeforeEnter),
|
createVirtualRoute(routeData.LANDING, landingBeforeEnter),
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,7 @@ const getDefaultState = () => {
|
||||||
capabilityQuestionAnswers: null,
|
capabilityQuestionAnswers: null,
|
||||||
dateOfLoss: null,
|
dateOfLoss: null,
|
||||||
damageCause: null,
|
damageCause: null,
|
||||||
|
installOemGlass: null,
|
||||||
},
|
},
|
||||||
lineItems: {
|
lineItems: {
|
||||||
glassParts: null,
|
glassParts: null,
|
||||||
|
|
@ -189,6 +190,7 @@ const getDefaultState = () => {
|
||||||
lockToken: null,
|
lockToken: null,
|
||||||
settledTenderAmount: 0,
|
settledTenderAmount: 0,
|
||||||
isRecalAckOptIn: false,
|
isRecalAckOptIn: false,
|
||||||
|
isRecalAcknowledgedForScheduling: "",
|
||||||
isMSRFeeApplicable: false,
|
isMSRFeeApplicable: false,
|
||||||
},
|
},
|
||||||
applicationUser: {
|
applicationUser: {
|
||||||
|
|
@ -342,6 +344,12 @@ export const mutations = {
|
||||||
updateIsRecalAckOptIn(state, isRecalAckOptIn) {
|
updateIsRecalAckOptIn(state, isRecalAckOptIn) {
|
||||||
state.order.isRecalAckOptIn = isRecalAckOptIn;
|
state.order.isRecalAckOptIn = isRecalAckOptIn;
|
||||||
},
|
},
|
||||||
|
updateIsRecalAcknowledgedForScheduling(state, isRecalAcknowledgedForScheduling) {
|
||||||
|
state.order.isRecalAcknowledgedForScheduling = isRecalAcknowledgedForScheduling;
|
||||||
|
},
|
||||||
|
updateIsOemGlassSelected(state, isOemGlassSelected) {
|
||||||
|
state.order.damage.installOemGlass = isOemGlassSelected;
|
||||||
|
},
|
||||||
updateIsMSRFeeApplicable(state, isMSRFeeApplicable) {
|
updateIsMSRFeeApplicable(state, isMSRFeeApplicable) {
|
||||||
state.order.isMSRFeeApplicable = isMSRFeeApplicable;
|
state.order.isMSRFeeApplicable = isMSRFeeApplicable;
|
||||||
},
|
},
|
||||||
|
|
@ -508,6 +516,10 @@ export const mutations = {
|
||||||
externalParameterState.vehicle.style = style;
|
externalParameterState.vehicle.style = style;
|
||||||
saveExternalParameterState(externalParameterState);
|
saveExternalParameterState(externalParameterState);
|
||||||
},
|
},
|
||||||
|
updateExternalParameterQsStash(state, qsStash) {
|
||||||
|
externalParameterState.qsStash = qsStash;
|
||||||
|
saveExternalParameterState(externalParameterState);
|
||||||
|
},
|
||||||
updateExternalParameterIsRepair(state, isRepair) {
|
updateExternalParameterIsRepair(state, isRepair) {
|
||||||
externalParameterState.vehicleDamage.isRepair = isRepair;
|
externalParameterState.vehicleDamage.isRepair = isRepair;
|
||||||
saveExternalParameterState(externalParameterState);
|
saveExternalParameterState(externalParameterState);
|
||||||
|
|
@ -712,6 +724,8 @@ export const mutations = {
|
||||||
state.order.damage.capabilityQuestionAnswers =
|
state.order.damage.capabilityQuestionAnswers =
|
||||||
sessionInformation.order.damage.capabilityQuestionAnswers;
|
sessionInformation.order.damage.capabilityQuestionAnswers;
|
||||||
|
|
||||||
|
state.order.damage.installOemGlass = sessionInformation.order.damage.installOemGlass;
|
||||||
|
|
||||||
state.order.lineItems.glassParts = sessionInformation.order.lineItems.glassParts;
|
state.order.lineItems.glassParts = sessionInformation.order.lineItems.glassParts;
|
||||||
state.order.lineItems.supportingItems = sessionInformation.order.lineItems.supportingItems;
|
state.order.lineItems.supportingItems = sessionInformation.order.lineItems.supportingItems;
|
||||||
state.order.lineItems.vaps = sessionInformation.order.lineItems.vaps ?? [];
|
state.order.lineItems.vaps = sessionInformation.order.lineItems.vaps ?? [];
|
||||||
|
|
@ -1765,7 +1779,8 @@ export const actions = {
|
||||||
const glassArray = damage.glassToReplace;
|
const glassArray = damage.glassToReplace;
|
||||||
const zipCode = order.serviceLocation.zipCode;
|
const zipCode = order.serviceLocation.zipCode;
|
||||||
const vin = vehicle.vin;
|
const vin = vehicle.vin;
|
||||||
const serviceType = order.serviceLocation?.appointmentType;
|
const serviceType = damage.isRepair ? "Repair" : "Install";
|
||||||
|
const parentAccountNumber = context.getters.payment.parentAccountNumber;
|
||||||
const referralSeqNumber = order.referralSequenceNumber;
|
const referralSeqNumber = order.referralSequenceNumber;
|
||||||
|
|
||||||
// create a new array to avoid mutating state
|
// create a new array to avoid mutating state
|
||||||
|
|
@ -1781,6 +1796,7 @@ export const actions = {
|
||||||
vin: vin,
|
vin: vin,
|
||||||
serviceType: serviceType,
|
serviceType: serviceType,
|
||||||
referralSeqNumber: referralSeqNumber,
|
referralSeqNumber: referralSeqNumber,
|
||||||
|
parentAccountNumber: parentAccountNumber,
|
||||||
},
|
},
|
||||||
logApiCall: true,
|
logApiCall: true,
|
||||||
pageNameToLog: pageNameToLog,
|
pageNameToLog: pageNameToLog,
|
||||||
|
|
@ -1836,6 +1852,55 @@ export const actions = {
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Parts API Actions
|
||||||
|
async getOemAfterMarketParts(context, { pageNameToLog }) {
|
||||||
|
const vehicle = context.getters.vehicle;
|
||||||
|
const damage = context.getters.damage;
|
||||||
|
const order = context.state.order;
|
||||||
|
|
||||||
|
const carId = vehicle.carId;
|
||||||
|
const glassArray = damage.glassToReplace;
|
||||||
|
const resultsArray = damage.partQuestionAnswers;
|
||||||
|
const zipCode = order.serviceLocation.zipCode;
|
||||||
|
const vin = vehicle.vin;
|
||||||
|
const make = vehicle.make;
|
||||||
|
const serviceType = order.serviceLocation?.appointmentType;
|
||||||
|
const referralSeqNumber = order.referralSequenceNumber;
|
||||||
|
const parentAccountNumber = applicationConfig.CASH_PARENT_ACCOUNT_NUMBER;
|
||||||
|
const oemEndorsementFlag = order.damage.installOemGlass || false;
|
||||||
|
// create a new array to avoid mutating state
|
||||||
|
const glassArrayForPayload = convertGlassPieceNamingForApi(glassArray);
|
||||||
|
const resultsArrayForPayload = convertResultsForApi(resultsArray);
|
||||||
|
|
||||||
|
const response = await globalMethods.callHttpClient({
|
||||||
|
method: endpoints.GetOemAfterMarketParts.method,
|
||||||
|
endpoint: endpoints.GetOemAfterMarketParts.url,
|
||||||
|
payload: {
|
||||||
|
carId: carId,
|
||||||
|
glassPieces: glassArrayForPayload,
|
||||||
|
answerResults: resultsArrayForPayload,
|
||||||
|
zip: zipCode,
|
||||||
|
vin: vin,
|
||||||
|
serviceType: serviceType,
|
||||||
|
referralSeqNumber: referralSeqNumber,
|
||||||
|
make: make,
|
||||||
|
parentAccountNumber: parentAccountNumber.toString(),
|
||||||
|
oemEndorsementFlag: oemEndorsementFlag,
|
||||||
|
},
|
||||||
|
logApiCall: true,
|
||||||
|
pageNameToLog: pageNameToLog,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Flatten location and name properties
|
||||||
|
if (response?.data?.glassPieceParts) {
|
||||||
|
response.data.glassPieceParts = convertGlassPieceNamingFromApi(
|
||||||
|
response.data.glassPieceParts
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
async getWipers(context, { payload, pageNameToLog }) {
|
async getWipers(context, { payload, pageNameToLog }) {
|
||||||
const carId = payload.carId;
|
const carId = payload.carId;
|
||||||
const serviceZipCode = payload.serviceZipCode;
|
const serviceZipCode = payload.serviceZipCode;
|
||||||
|
|
@ -1936,26 +2001,6 @@ export const actions = {
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getServicePackageDiscountPart(context, { pageNameToLog }) {
|
|
||||||
const damage = context.getters.damage;
|
|
||||||
const damageType = damage.isRepair ? "Repair" : "Replace";
|
|
||||||
const glassPieces = convertGlassPieceNamingForApi(damage.glassToReplace);
|
|
||||||
|
|
||||||
return globalMethods
|
|
||||||
.callHttpClient({
|
|
||||||
method: endpoints.GetServicePackageDiscountPart.method,
|
|
||||||
endpoint: endpoints.GetServicePackageDiscountPart.url,
|
|
||||||
payload: {
|
|
||||||
glassPieces: glassPieces,
|
|
||||||
damageType: damageType,
|
|
||||||
},
|
|
||||||
logApiCall: true,
|
|
||||||
pageNameToLog: pageNameToLog,
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
return response;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getServiceabilityDetails(context, { payload: { serviceZipCode }, pageNameToLog }) {
|
getServiceabilityDetails(context, { payload: { serviceZipCode }, pageNameToLog }) {
|
||||||
const escapeRecalibrationType = (rt) => rt.split("&").join("%26");
|
const escapeRecalibrationType = (rt) => rt.split("&").join("%26");
|
||||||
|
|
@ -2421,6 +2466,7 @@ export const actions = {
|
||||||
partQuestionAnswers: order.damage.partQuestionAnswers,
|
partQuestionAnswers: order.damage.partQuestionAnswers,
|
||||||
moldingQuestionAnswers: order.damage.moldingQuestionAnswers,
|
moldingQuestionAnswers: order.damage.moldingQuestionAnswers,
|
||||||
capabilityQuestionAnswers: order.damage.capabilityQuestionAnswers,
|
capabilityQuestionAnswers: order.damage.capabilityQuestionAnswers,
|
||||||
|
installOemGlass: order.damage.installOemGlass,
|
||||||
},
|
},
|
||||||
lineItems: {
|
lineItems: {
|
||||||
glassParts: lineItems.glassParts,
|
glassParts: lineItems.glassParts,
|
||||||
|
|
@ -2927,6 +2973,17 @@ export const actions = {
|
||||||
context.commit(storeMutations.UPDATE_IS_RECAL_ACK_OPT_IN, isRecalAckOptIn);
|
context.commit(storeMutations.UPDATE_IS_RECAL_ACK_OPT_IN, isRecalAckOptIn);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
saveIsRecalAcknowledgedForScheduling(context, isRecalAcknowledgedForScheduling) {
|
||||||
|
context.commit(
|
||||||
|
storeMutations.UPDATE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING,
|
||||||
|
isRecalAcknowledgedForScheduling
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
saveIsOemGlassSelected(context, isOemGlassSelected) {
|
||||||
|
context.commit(storeMutations.UPDATE_IS_OEM_GLASS_SELECTED, isOemGlassSelected);
|
||||||
|
},
|
||||||
|
|
||||||
saveIsMSRFeeApplicable(context, isMSRFeeApplicable) {
|
saveIsMSRFeeApplicable(context, isMSRFeeApplicable) {
|
||||||
context.commit(storeMutations.UPDATE_IS_MSR_FEE_APPLICABLE, isMSRFeeApplicable);
|
context.commit(storeMutations.UPDATE_IS_MSR_FEE_APPLICABLE, isMSRFeeApplicable);
|
||||||
},
|
},
|
||||||
|
|
@ -3093,40 +3150,24 @@ export const actions = {
|
||||||
promoCode: lineItem.promoCode ?? null,
|
promoCode: lineItem.promoCode ?? null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const pricedLineItemsFormattedForRequest =
|
const order = context.getters.order;
|
||||||
buildQueryStringParameterFromArrayOfComplexObjects(
|
|
||||||
lineItemsWithOnlyPriceInfo,
|
|
||||||
"lineItems"
|
|
||||||
);
|
|
||||||
|
|
||||||
let queryString = "";
|
|
||||||
if (appointmentType == "Mobile") {
|
|
||||||
queryString =
|
|
||||||
`ParentAccountNumber=${context.getters.order.payment.parentAccountNumber}` +
|
|
||||||
`&BillToAccountNumber=${billToAccountNumber}` +
|
|
||||||
`&ProviderNumber=${providerNumber}` +
|
|
||||||
`&AppointmentType=${appointmentType}` +
|
|
||||||
`&ServiceLocation.City=${serviceLocationCity}` +
|
|
||||||
`&ServiceLocation.State=${serviceLocationState}` +
|
|
||||||
`&ServiceLocation.ZipCode=${serviceLocationZipCode}` +
|
|
||||||
`&${pricedLineItemsFormattedForRequest}`;
|
|
||||||
} else {
|
|
||||||
queryString =
|
|
||||||
`ParentAccountNumber=${context.getters.order.payment.parentAccountNumber}` +
|
|
||||||
`&BillToAccountNumber=${billToAccountNumber}` +
|
|
||||||
`&ProviderNumber=${providerNumber}` +
|
|
||||||
`&AppointmentType=${appointmentType}` +
|
|
||||||
`&${pricedLineItemsFormattedForRequest}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lineItemServerData = context.getters.order.lineItems.serverData;
|
|
||||||
if (lineItemServerData) {
|
|
||||||
queryString += `&ServerData=${encodeURIComponent(lineItemServerData)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await globalMethods.callHttpClient({
|
const response = await globalMethods.callHttpClient({
|
||||||
method: endpoints.TaxOrderItems.method,
|
method: endpoints.TaxOrderItems.method,
|
||||||
endpoint: `${endpoints.TaxOrderItems.url}?${queryString}`,
|
endpoint: endpoints.TaxOrderItems.url,
|
||||||
|
payload: {
|
||||||
|
parentAccountNumber: order.payment.parentAccountNumber,
|
||||||
|
billToAccountNumber: billToAccountNumber,
|
||||||
|
providerNumber: providerNumber,
|
||||||
|
appointmentType: appointmentType,
|
||||||
|
pricedLineItems: lineItemsWithOnlyPriceInfo,
|
||||||
|
serviceLocation: {
|
||||||
|
city: appointmentType == "Mobile" ? serviceLocationCity : null,
|
||||||
|
state: appointmentType == "Mobile" ? serviceLocationState : null,
|
||||||
|
zipCode: appointmentType == "Mobile" ? serviceLocationZipCode : null,
|
||||||
|
},
|
||||||
|
serverData: order.lineItems.serverData ? order.lineItems.serverData : "",
|
||||||
|
},
|
||||||
logApiCall: true,
|
logApiCall: true,
|
||||||
pageNameToLog: pageNameToLog,
|
pageNameToLog: pageNameToLog,
|
||||||
});
|
});
|
||||||
|
|
@ -4105,6 +4146,7 @@ function createExternalParameterDefaultState() {
|
||||||
// create default externalParameter state
|
// create default externalParameter state
|
||||||
const externalParameterDefaultState = {
|
const externalParameterDefaultState = {
|
||||||
isExternalParameter: externalParameterStatus.NOT_SET,
|
isExternalParameter: externalParameterStatus.NOT_SET,
|
||||||
|
qsStash: null,
|
||||||
vehicle: {
|
vehicle: {
|
||||||
year: null,
|
year: null,
|
||||||
make: null,
|
make: null,
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ $red-600: #ac160b;
|
||||||
$red-700: #840900;
|
$red-700: #840900;
|
||||||
$red-800: #5b0600;
|
$red-800: #5b0600;
|
||||||
$red-900: #330300;
|
$red-900: #330300;
|
||||||
|
$red-1000: #a80019;
|
||||||
|
$red-1100: #791710;
|
||||||
|
|
||||||
// Greens
|
// Greens
|
||||||
$green-100: #e3f2ea;
|
$green-100: #e3f2ea;
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<a v-else-if="linkType === 'textSmall'" @click="handleClick" class="small" :href="href">{{ text }}<slot name="after-text"></slot></a>
|
<a v-else-if="linkType === 'textSmall'" @click="handleClick" class="small" :href="href">{{ text }}<slot name="after-text"></slot></a>
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<a v-else-if="linkType === 'text'" @click="handleClick" :href="href">{{ text }}<slot name="after-text"></slot></a>
|
<a v-else-if="linkType === 'text'" @click="handleClick" class="text-link" :href="href">{{ text }}<slot name="after-text"></slot></a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -89,5 +89,8 @@ a {
|
||||||
&.new-window-link {
|
&.new-window-link {
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
&.text-link {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue