Merge branch 'release/2025.11.20' into rlsmerge/2025.11.20-to-develop
This commit is contained in:
commit
3f27c2205f
19 changed files with 671 additions and 324 deletions
|
|
@ -114,7 +114,7 @@ body {
|
||||||
debug: true,
|
debug: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Test',
|
name: 'SysTest',
|
||||||
hostName: 'www-test2.safelite.com',
|
hostName: 'www-test2.safelite.com',
|
||||||
apiHostname: 'digitalapi.test.safelite.io',
|
apiHostname: 'digitalapi.test.safelite.io',
|
||||||
debug: true,
|
debug: true,
|
||||||
|
|
@ -142,83 +142,178 @@ body {
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getExperimentSettingsFromVuex(vuexData) {
|
||||||
|
const experiments = vuexData?.applicationUser?.experiments;
|
||||||
|
|
||||||
|
if(experiments) {
|
||||||
|
return experiments
|
||||||
|
.filter((e) => !!e.isActive)
|
||||||
|
.map((e) => e.settings)
|
||||||
|
.reduce((prev, next) => Object.assign(prev, next), {}) ?? {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractDataFromVuex(existingVuexData) {
|
||||||
|
let bailoutInfo = [];
|
||||||
|
let headerInfo = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// =============== Collect diagnostic data:
|
||||||
|
bailoutInfo = [];
|
||||||
|
|
||||||
|
// App & Site Name
|
||||||
|
bailoutInfo.push({ name: 'App Name', value: 'FixMyGlass' });
|
||||||
|
bailoutInfo.push({ name: 'Site Name', value: 'SafeliteDotcom' });
|
||||||
|
|
||||||
|
// Not included (yet) from heritage:
|
||||||
|
// - Site ID
|
||||||
|
// - Code
|
||||||
|
// - Module
|
||||||
|
// - Page
|
||||||
|
|
||||||
|
bailoutInfo.push({ name: 'Timestamp', value: `${new Date()}` });
|
||||||
|
|
||||||
|
// - URL
|
||||||
|
// - Server
|
||||||
|
|
||||||
|
const sessionId = getCookieValueByName('sid');
|
||||||
|
bailoutInfo.push({ name: 'Session Id', value: sessionId });
|
||||||
|
|
||||||
|
const userAgent = window.navigator.userAgent;
|
||||||
|
bailoutInfo.push({ name: 'User Agent', value: userAgent });
|
||||||
|
|
||||||
|
// - IP
|
||||||
|
// - Session Log Sequence Number
|
||||||
|
|
||||||
|
bailoutInfo.push({ name: 'Referral Seq Number', value: existingVuexData.order?.referralSequenceNumber });
|
||||||
|
bailoutInfo.push({ name: 'Referral Number', value: existingVuexData.order?.referralNumber });
|
||||||
|
bailoutInfo.push({ name: 'Referral Date', value: existingVuexData.order?.referralDate });
|
||||||
|
bailoutInfo.push({ name: 'Referral Provider Number', value: existingVuexData.order?.serviceLocation?.provider?.providerNumber });
|
||||||
|
bailoutInfo.push({ name: 'Referral CTU', value: existingVuexData.order?.serviceLocation?.provider?.address?.zipCodeCtu });
|
||||||
|
|
||||||
|
// - Referral Insured Zip
|
||||||
|
// - Referral Insured Service Zip
|
||||||
|
|
||||||
|
bailoutInfo.push({ name: 'Referral CarID', value: existingVuexData.order?.vehicle?.carId });
|
||||||
|
|
||||||
|
const vehicle = existingVuexData.order?.vehicle;
|
||||||
|
const vehicleString = vehicle?.year
|
||||||
|
? `${vehicle.year} ${vehicle.make} ${vehicle.model} - ${vehicle.style}`
|
||||||
|
: null;
|
||||||
|
bailoutInfo.push({ name: 'Referral Vehicle', value: vehicleString });
|
||||||
|
bailoutInfo.push({ name: 'Work Order Number', value: existingVuexData.order?.workOrderNumber });
|
||||||
|
bailoutInfo.push({ name: 'Work Order ID', value: existingVuexData.order?.workOrderId });
|
||||||
|
bailoutInfo.push({ name: 'Parent Account Number', value: existingVuexData.order?.payment?.parentAccountNumber });
|
||||||
|
|
||||||
|
const environmentData = getCurrentEnvironmentData();
|
||||||
|
const userIdCookieName = `FunnelUserId-${environmentData?.name}`;
|
||||||
|
bailoutInfo.push({ name: 'User Id', value: getCookieValueByName(userIdCookieName) });
|
||||||
|
|
||||||
|
// - Parent Account Name
|
||||||
|
// - Client GUID
|
||||||
|
|
||||||
|
// =============== Collect header data:
|
||||||
|
headerInfo = [];
|
||||||
|
|
||||||
|
headerInfo.push({ name: 'X-Experiment-Data', value: JSON.stringify(getExperimentSettingsFromVuex(existingVuexData)) });
|
||||||
|
headerInfo.push({ name: 'X-Application-Name', value: 'FixMyGlass' });
|
||||||
|
|
||||||
|
const sessionKeyCookieName = `FunnelSessionKey-${environmentData?.name}`;
|
||||||
|
|
||||||
|
headerInfo.push({ name: 'X-Session-Sequence-Number', value: getCookieValueByName(sessionKeyCookieName) });
|
||||||
|
headerInfo.push({ name: 'X-Referral-Sequence-Number', value: existingVuexData?.order?.referralSequenceNumber });
|
||||||
|
headerInfo.push({ name: 'X-Enterprise-Order-Number', value: existingVuexData?.order?.eon });
|
||||||
|
headerInfo.push({ name: 'log-enabled', value: existingVuexData?.applicationUser?.loggingOption ?? false });
|
||||||
|
} finally {
|
||||||
|
return {
|
||||||
|
headerInfo: headerInfo,
|
||||||
|
bailoutInfo: bailoutInfo
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateRequestHeader(headerInfo) {
|
||||||
|
let headers = {};
|
||||||
|
|
||||||
|
headerInfo.forEach(
|
||||||
|
keyPair => {
|
||||||
|
headers[keyPair.name] = keyPair.value;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
headers['X-Transaction-Id'] = crypto.randomUUID();
|
||||||
|
|
||||||
|
headers['Content-Type'] = 'application/json';
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFieldWithName(info, name) {
|
||||||
|
const match = info?.find(
|
||||||
|
(row) => row.name === name
|
||||||
|
);
|
||||||
|
|
||||||
|
return match?.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearApplicationData() {
|
||||||
|
window.localStorage.removeItem('vuex');
|
||||||
|
|
||||||
|
const environmentData = getCurrentEnvironmentData();
|
||||||
|
const cookieNames = [
|
||||||
|
`FunnelUserId-${environmentData?.name}`,
|
||||||
|
`FunnelSessionKey-${environmentData?.name}`,
|
||||||
|
`FunnelSessionInfo-${environmentData?.name}`,
|
||||||
|
`sid`,
|
||||||
|
`dxdev`,
|
||||||
|
];
|
||||||
|
|
||||||
|
cookieNames.forEach(
|
||||||
|
(cookieName) => {
|
||||||
|
let cookieToAdd = `${cookieName}=undefined; path=/; domain=${environmentData.hostName}; max-age=0`;
|
||||||
|
document.cookie = cookieToAdd;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
window.onload = async () => {
|
window.onload = async () => {
|
||||||
// =============== Check for session info:
|
// =============== Check for session info:
|
||||||
const existingVuexDataJSON = window.localStorage.getItem('vuex');
|
const existingVuexDataJSON = window.localStorage.getItem('vuex');
|
||||||
const existingVuexData = existingVuexDataJSON ? JSON.parse(existingVuexDataJSON) : null;
|
const existingVuexData = existingVuexDataJSON ? JSON.parse(existingVuexDataJSON) : null;
|
||||||
const existingBailoutInfoJSON = window.sessionStorage.getItem('bailoutInfo');
|
const existingBailoutInfoJSON = window.sessionStorage.getItem('bailoutInfo');
|
||||||
const existingBailoutInfo = existingBailoutInfoJSON ? JSON.parse(existingBailoutInfoJSON) : null;
|
const existingBailoutInfo = existingBailoutInfoJSON ? JSON.parse(existingBailoutInfoJSON) : null;
|
||||||
|
const existingHeaderInfoJSON = window.sessionStorage.getItem('headerInfo');
|
||||||
|
const existingHeaderInfo = existingHeaderInfoJSON ? JSON.parse(existingHeaderInfoJSON) : null;
|
||||||
|
|
||||||
console.log(`================ VUEX DATA`);
|
console.log(`================ VUEX DATA`);
|
||||||
console.log(existingVuexData);
|
console.log(existingVuexData);
|
||||||
console.log(`================ PRIOR BAILOUT DATA`);
|
console.log(`================ PRIOR BAILOUT DATA`);
|
||||||
console.log(existingBailoutInfo);
|
console.log(existingBailoutInfo);
|
||||||
|
console.log(`================ PRIOR HEADER DATA`);
|
||||||
|
console.log(existingHeaderInfo);
|
||||||
|
|
||||||
let bailoutInfo = null;
|
let bailoutInfo = null;
|
||||||
|
let headerInfo = null;
|
||||||
|
|
||||||
if(existingVuexData) {
|
if(existingVuexData) {
|
||||||
try {
|
const results = extractDataFromVuex(existingVuexData);
|
||||||
// =============== Collect diagnostic data:
|
bailoutInfo = results.bailoutInfo;
|
||||||
bailoutInfo = [];
|
headerInfo = results.headerInfo;
|
||||||
|
|
||||||
// App & Site Name
|
clearApplicationData();
|
||||||
bailoutInfo.push({ name: 'App Name', value: 'FixMyGlass' });
|
|
||||||
bailoutInfo.push({ name: 'Site Name', value: 'SafeliteDotcom' });
|
|
||||||
|
|
||||||
// Not included (yet) from heritage:
|
if(bailoutInfo) {
|
||||||
// - Site ID
|
window.sessionStorage.setItem('bailoutInfo', JSON.stringify(bailoutInfo));
|
||||||
// - Code
|
|
||||||
// - Module
|
|
||||||
// - Page
|
|
||||||
|
|
||||||
bailoutInfo.push({ name: 'Timestamp', value: `${new Date()}` });
|
|
||||||
|
|
||||||
// - URL
|
|
||||||
// - Server
|
|
||||||
|
|
||||||
const sessionId = getCookieValueByName('sid');
|
|
||||||
bailoutInfo.push({ name: 'Session Id', value: sessionId });
|
|
||||||
|
|
||||||
const userAgent = window.navigator.userAgent;
|
|
||||||
bailoutInfo.push({ name: 'User Agent', value: userAgent });
|
|
||||||
|
|
||||||
// - IP
|
|
||||||
// - Session Log Sequence Number
|
|
||||||
|
|
||||||
bailoutInfo.push({ name: 'Referral Seq Number', value: existingVuexData.order?.referralSequenceNumber });
|
|
||||||
bailoutInfo.push({ name: 'Referral Number', value: existingVuexData.order?.referralNumber });
|
|
||||||
bailoutInfo.push({ name: 'Referral Date', value: existingVuexData.order?.referralDate });
|
|
||||||
bailoutInfo.push({ name: 'Referral Provider Number', value: existingVuexData.order?.serviceLocation?.provider?.providerNumber });
|
|
||||||
bailoutInfo.push({ name: 'Referral CTU', value: existingVuexData.order?.serviceLocation?.provider?.address?.zipCodeCtu });
|
|
||||||
|
|
||||||
// - Referral Insured Zip
|
|
||||||
// - Referral Insured Service Zip
|
|
||||||
|
|
||||||
bailoutInfo.push({ name: 'Referral CarID', value: existingVuexData.order?.vehicle?.carId });
|
|
||||||
|
|
||||||
const vehicle = existingVuexData.order?.vehicle;
|
|
||||||
const vehicleString = vehicle?.year
|
|
||||||
? `${vehicle.year} ${vehicle.make} ${vehicle.model} - ${vehicle.style}`
|
|
||||||
: null;
|
|
||||||
bailoutInfo.push({ name: 'Referral Vehicle', value: vehicleString });
|
|
||||||
bailoutInfo.push({ name: 'Work Order Number', value: existingVuexData.order?.workOrderNumber });
|
|
||||||
bailoutInfo.push({ name: 'Work Order ID', value: existingVuexData.order?.workOrderId });
|
|
||||||
bailoutInfo.push({ name: 'Parent Account Number', value: existingVuexData.order?.payment?.parentAccountNumber });
|
|
||||||
|
|
||||||
// - Parent Account Name
|
|
||||||
// - Client GUID
|
|
||||||
} finally {
|
|
||||||
// If any information gathered, write to session storage.
|
|
||||||
if(bailoutInfo) {
|
|
||||||
window.sessionStorage.setItem('bailoutInfo', JSON.stringify(bailoutInfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then *always* clear vuex data.
|
|
||||||
window.localStorage.removeItem('vuex');
|
|
||||||
}
|
}
|
||||||
} else if(existingBailoutInfo) {
|
|
||||||
// Proceed with prior data.
|
if(headerInfo) {
|
||||||
bailoutInfo = existingBailoutInfo;
|
window.sessionStorage.setItem('headerInfo', JSON.stringify(headerInfo));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bailoutInfo = existingBailoutInfo ?? [];
|
||||||
|
headerInfo = existingHeaderInfo ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const environmentInfo = getCurrentEnvironmentData();
|
const environmentInfo = getCurrentEnvironmentData();
|
||||||
|
|
@ -275,11 +370,11 @@ body {
|
||||||
);
|
);
|
||||||
const entryString = `User encountered bailout page.\n${new Date()}\n${infoString ?? 'No information recoverable'}`;
|
const entryString = `User encountered bailout page.\n${new Date()}\n${infoString ?? 'No information recoverable'}`;
|
||||||
|
|
||||||
|
const headers = generateRequestHeader(headerInfo);
|
||||||
|
|
||||||
const request = new Request(endpointUrl, {
|
const request = new Request(endpointUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: headers,
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
entry: entryString,
|
entry: entryString,
|
||||||
}),
|
}),
|
||||||
|
|
@ -292,6 +387,38 @@ body {
|
||||||
console.error(`=== ERROR SENDING LOGGING`);
|
console.error(`=== ERROR SENDING LOGGING`);
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const endpointUrl = `https://${apiHostname}/analytics/api/v1/analytics/log-page-view`;
|
||||||
|
|
||||||
|
const headers = generateRequestHeader(headerInfo);
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
AppName: "FixMyGlass",
|
||||||
|
action: "",
|
||||||
|
applicationName: "FixMyGlassNextGen",
|
||||||
|
event: "ENTRY",
|
||||||
|
experimentsForUser: [],
|
||||||
|
pageName: "static-error",
|
||||||
|
parentAccountNumber: getFieldWithName(bailoutInfo, 'Parent Account Number') ?? 0,
|
||||||
|
referralSequenceNumber: getFieldWithName(bailoutInfo, 'Referral Seq Number'),
|
||||||
|
sessionId: getFieldWithName(bailoutInfo, 'Session Id'),
|
||||||
|
sessionKey: getFieldWithName(headerInfo, 'X-Session-Sequence-Number'),
|
||||||
|
shouldUseSessionId: false,
|
||||||
|
userId: getFieldWithName(bailoutInfo, 'User Id'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const request = new Request(endpointUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: headers,
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch(request);
|
||||||
|
} catch(e) {
|
||||||
|
console.error(`=== ERROR SENDING PAGEVIEW`);
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ const cartItemCategories = {
|
||||||
GLASS_PARTS: "glassParts",
|
GLASS_PARTS: "glassParts",
|
||||||
SUPPORTING_ITEMS: "supportingItems",
|
SUPPORTING_ITEMS: "supportingItems",
|
||||||
PROMOS: "promos",
|
PROMOS: "promos",
|
||||||
SERVICE_PACKAGE_DISCOUNT: "servicePackageDiscount",
|
QUOTE_PAGE_DISCOUNT: "quotePageDiscount",
|
||||||
};
|
};
|
||||||
|
|
||||||
export { cartItemCategories };
|
export { cartItemCategories };
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ const cartItemTypes = {
|
||||||
PROMOS: "PROMOS",
|
PROMOS: "PROMOS",
|
||||||
EARLY_BIRD: "EARLY BIRD",
|
EARLY_BIRD: "EARLY BIRD",
|
||||||
SUPPLIES_REPAIR: "SUPPLIES-REPAIR",
|
SUPPLIES_REPAIR: "SUPPLIES-REPAIR",
|
||||||
SERVICE_PACKAGE_DISCOUNT: "SERVICE PACKAGE DISCOUNT",
|
QUOTE_PAGE_DISCOUNT: "QUOTE PAGE DISCOUNT",
|
||||||
DONATION: "DONATION",
|
DONATION: "DONATION",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ const partTypeStrings = {
|
||||||
REPAIR_FEE: "REPAIR FEE",
|
REPAIR_FEE: "REPAIR FEE",
|
||||||
EARLY_BIRD: "EARLY BIRD",
|
EARLY_BIRD: "EARLY BIRD",
|
||||||
SERVICE_PACKAGE_DISCOUNT: "SERVICE PACKAGE DISCOUNT",
|
SERVICE_PACKAGE_DISCOUNT: "SERVICE PACKAGE DISCOUNT",
|
||||||
|
QUOTE_PAGE_DISCOUNT: "QUOTE PAGE DISCOUNT",
|
||||||
DONATION: "DONATION",
|
DONATION: "DONATION",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
80
src/constants/quote-page-discounts.js
Normal file
80
src/constants/quote-page-discounts.js
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
import { packageNames } from "./package-names";
|
||||||
|
import { partTypeStrings } from "./part-type-strings";
|
||||||
|
|
||||||
|
export const quotePageDiscountTable = [
|
||||||
|
// Repair
|
||||||
|
{
|
||||||
|
name: "Repair Glass Only",
|
||||||
|
experimentCode: "Show_GlassOnly_Repair_Discount",
|
||||||
|
partNumber: "GL RPR CSH PKG",
|
||||||
|
partType: partTypeStrings.QUOTE_PAGE_DISCOUNT,
|
||||||
|
description: "GLASS RPR GEOTARGET DISC",
|
||||||
|
packageLevel: packageNames.TIER_ONE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Repair Standard",
|
||||||
|
experimentCode: "Show_Standard_Repair_Discount",
|
||||||
|
partNumber: "STD RPR CSH PKG",
|
||||||
|
partType: partTypeStrings.QUOTE_PAGE_DISCOUNT,
|
||||||
|
description: "STD RPR GEOTARGET DISC",
|
||||||
|
packageLevel: packageNames.TIER_TWO,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Repair Premium",
|
||||||
|
experimentCode: "Show_Premium_Repair_Discount",
|
||||||
|
partNumber: "PRM RPR CSH PKG",
|
||||||
|
partType: partTypeStrings.QUOTE_PAGE_DISCOUNT,
|
||||||
|
description: "PREM RPR GEOTARGET DISC",
|
||||||
|
packageLevel: packageNames.TIER_THREE,
|
||||||
|
},
|
||||||
|
// Replace - No recal
|
||||||
|
{
|
||||||
|
name: "Replace Glass Only",
|
||||||
|
experimentCode: "Show_GlassOnly_Replace_Discount",
|
||||||
|
partNumber: "GL RPL CSH PKG",
|
||||||
|
partType: partTypeStrings.QUOTE_PAGE_DISCOUNT,
|
||||||
|
description: "GLASS RPL GEOTARGET DISC",
|
||||||
|
packageLevel: packageNames.TIER_ONE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Replace Standard",
|
||||||
|
experimentCode: "Show_Standard_Replace_Discount",
|
||||||
|
partNumber: "STD RPL CSH PKG",
|
||||||
|
partType: partTypeStrings.QUOTE_PAGE_DISCOUNT,
|
||||||
|
description: "STD RPL GEOTARGET DISC",
|
||||||
|
packageLevel: packageNames.TIER_TWO,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Replace Premium",
|
||||||
|
experimentCode: "Show_Premium_Replace_Discount",
|
||||||
|
partNumber: "PRM RPL CSH PKG",
|
||||||
|
partType: partTypeStrings.QUOTE_PAGE_DISCOUNT,
|
||||||
|
description: "PREM RPL GEOTARGET DISC",
|
||||||
|
packageLevel: packageNames.TIER_THREE,
|
||||||
|
},
|
||||||
|
// Replace - with recal
|
||||||
|
{
|
||||||
|
name: "Recal Glass Only",
|
||||||
|
experimentCode: "Show_GlassOnly_Recal_Discount",
|
||||||
|
partNumber: "GL RCL CSH PKG",
|
||||||
|
partType: partTypeStrings.QUOTE_PAGE_DISCOUNT,
|
||||||
|
description: "GLASS RCL GEOTARGET DISC",
|
||||||
|
packageLevel: packageNames.TIER_ONE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Recal Standard",
|
||||||
|
experimentCode: "Show_Standard_Recal_Discount",
|
||||||
|
partNumber: "STD RCL CSH PKG",
|
||||||
|
partType: partTypeStrings.QUOTE_PAGE_DISCOUNT,
|
||||||
|
description: "STD RCL GEOTARGET DISC",
|
||||||
|
packageLevel: packageNames.TIER_TWO,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Recal Premium",
|
||||||
|
experimentCode: "Show_Premium_Recal_Discount",
|
||||||
|
partNumber: "PRM RCL CSH PKG",
|
||||||
|
partType: partTypeStrings.QUOTE_PAGE_DISCOUNT,
|
||||||
|
description: "PREM RCL GEOTARGET DISC",
|
||||||
|
packageLevel: packageNames.TIER_THREE,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<slot name="modal-footer-slot"></slot>
|
<slot name="modal-footer-slot"></slot>
|
||||||
<modalButtonMain
|
<modalButtonMain
|
||||||
|
v-if="!isFooterButtonSuppressed"
|
||||||
:isPrimary="isFooterButtonPrimary"
|
:isPrimary="isFooterButtonPrimary"
|
||||||
class="w-100 modal-footer-button"
|
class="w-100 modal-footer-button"
|
||||||
:id="modalId + '-modalbtn'"
|
:id="modalId + '-modalbtn'"
|
||||||
|
|
@ -67,6 +68,7 @@ export default {
|
||||||
staticBackdrop: Boolean,
|
staticBackdrop: Boolean,
|
||||||
footerButtonDisabled: Boolean,
|
footerButtonDisabled: Boolean,
|
||||||
isFooterButtonPrimary: Boolean,
|
isFooterButtonPrimary: Boolean,
|
||||||
|
isFooterButtonSuppressed: Boolean,
|
||||||
onModalOpenedCallback: {
|
onModalOpenedCallback: {
|
||||||
type: Function,
|
type: Function,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -145,9 +145,7 @@ export default {
|
||||||
return "$" + decimalPrice.toFixed(2);
|
return "$" + decimalPrice.toFixed(2);
|
||||||
},
|
},
|
||||||
hasPackageDiscount() {
|
hasPackageDiscount() {
|
||||||
return (
|
return this.additionalButtonData.hasDiscount && this.additionalButtonData.Text;
|
||||||
this.additionalButtonData.servicePackageDiscount && this.additionalButtonData.Text
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@
|
||||||
class="package-label pricing-by-day-pkg-lbl"
|
class="package-label pricing-by-day-pkg-lbl"
|
||||||
:class="[
|
:class="[
|
||||||
this.buttonLabelSubCopy ? 'has-subheader' : '',
|
this.buttonLabelSubCopy ? 'has-subheader' : '',
|
||||||
!this.additionalButtonData.servicePackageDiscount ? 'adjust-top' : '',
|
!this.additionalButtonData.hasDiscount ? 'adjust-top' : '',
|
||||||
]"
|
]"
|
||||||
for="testradio">
|
for="testradio">
|
||||||
<div class="package-specs">
|
<div class="package-specs">
|
||||||
<div v-if="this.additionalButtonData.servicePackageDiscount" class="row">
|
<div v-if="this.additionalButtonData.hasDiscount" class="row">
|
||||||
<div class="col md-6">
|
<div class="col md-6">
|
||||||
<p class="m-0">
|
<p class="m-0">
|
||||||
<span v-html="this.buttonLabel"></span>
|
<span v-html="this.buttonLabel"></span>
|
||||||
|
|
@ -84,10 +84,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="this.additionalButtonData.hasDiscount && this.additionalButtonData.Text"
|
||||||
this.additionalButtonData.servicePackageDiscount &&
|
|
||||||
this.additionalButtonData.Text
|
|
||||||
"
|
|
||||||
class="special-save-box">
|
class="special-save-box">
|
||||||
<span v-html="this.additionalButtonData.Text"></span>
|
<span v-html="this.additionalButtonData.Text"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,7 @@ import { cartItemCategories } from "@/constants/cart-item-categories";
|
||||||
import { cartItemTypes } from "@/constants/cart-item-types";
|
import { cartItemTypes } from "@/constants/cart-item-types";
|
||||||
import { coverageStatus, cartItemTypesCoveredByInsurance } from "@/constants/insurance";
|
import { coverageStatus, cartItemTypesCoveredByInsurance } from "@/constants/insurance";
|
||||||
import { experimentSettings } from "@/constants/experiments";
|
import { experimentSettings } from "@/constants/experiments";
|
||||||
|
import { quotePageDiscountTable } from "@/constants/quote-page-discounts";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "cart",
|
name: "cart",
|
||||||
|
|
@ -256,11 +257,11 @@ export default {
|
||||||
|
|
||||||
return subTotal;
|
return subTotal;
|
||||||
},
|
},
|
||||||
getServicePackageDiscount() {
|
getQuotePageDiscount() {
|
||||||
const servicePackageDiscountLineItems = this.servicePackageDiscountCartItem;
|
const quotePageDiscountLineItems = this.quotePageDiscountCartItem;
|
||||||
let subTotal = 0;
|
let subTotal = 0;
|
||||||
|
|
||||||
subTotal += servicePackageDiscountLineItems?.subTotal ?? 0;
|
subTotal += quotePageDiscountLineItems?.subTotal ?? 0;
|
||||||
return subTotal;
|
return subTotal;
|
||||||
},
|
},
|
||||||
getVapsCartItemsForSelectedPackage(packageName) {
|
getVapsCartItemsForSelectedPackage(packageName) {
|
||||||
|
|
@ -303,7 +304,8 @@ export default {
|
||||||
|
|
||||||
getLineItemAmount(amount, useVerifyingText, category) {
|
getLineItemAmount(amount, useVerifyingText, category) {
|
||||||
if (useVerifyingText) return this.verifyingCoverageText;
|
if (useVerifyingText) return this.verifyingCoverageText;
|
||||||
return category == "promos" || category == "servicePackageDiscount"
|
return category == cartItemCategories.PROMOS ||
|
||||||
|
category == cartItemCategories.QUOTE_PAGE_DISCOUNT
|
||||||
? "(" + this.currencyFormatter.format(amount * -1) + ")"
|
? "(" + this.currencyFormatter.format(amount * -1) + ")"
|
||||||
: this.currencyFormatter.format(amount);
|
: this.currencyFormatter.format(amount);
|
||||||
},
|
},
|
||||||
|
|
@ -316,16 +318,21 @@ export default {
|
||||||
if (category == cartItemCategories.VAPS || category == cartItemCategories.PROMOS) {
|
if (category == cartItemCategories.VAPS || category == cartItemCategories.PROMOS) {
|
||||||
this.saveVaps(this.lineItems);
|
this.saveVaps(this.lineItems);
|
||||||
// Check if service package discount should be removed after vaps change
|
// Check if service package discount should be removed after vaps change
|
||||||
if (
|
if (this.quotePageDiscountCartItem) {
|
||||||
this.servicePackageDiscountCartItem &&
|
const existingLineItem = this.quotePageDiscountCartItem.lineItems.at(0);
|
||||||
this.discountPackageNames != this.packageLevel
|
const discountInfo = quotePageDiscountTable.find(
|
||||||
) {
|
(info) => info.partNumber === existingLineItem?.partNumber
|
||||||
this.lineItems.supportingItems = this.lineItems.supportingItems.filter(
|
|
||||||
(lineItemsToKeep) =>
|
|
||||||
lineItemsToKeep.cartItemType !=
|
|
||||||
this.servicePackageDiscountCartItem.cartItemType
|
|
||||||
);
|
);
|
||||||
shouldSaveSupportingItems = true;
|
const packageLevelForDiscount = discountInfo?.packageLevel;
|
||||||
|
|
||||||
|
if (this.packageLevel !== packageLevelForDiscount) {
|
||||||
|
this.lineItems.supportingItems = this.lineItems.supportingItems.filter(
|
||||||
|
(lineItemsToKeep) =>
|
||||||
|
lineItemsToKeep.cartItemType !=
|
||||||
|
this.quotePageDiscountCartItem.cartItemType
|
||||||
|
);
|
||||||
|
shouldSaveSupportingItems = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (shouldSaveSupportingItems) {
|
if (shouldSaveSupportingItems) {
|
||||||
|
|
@ -483,8 +490,8 @@ export default {
|
||||||
cartItems.push(this.mobileFeeCartItem);
|
cartItems.push(this.mobileFeeCartItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.servicePackageDiscountCartItem) {
|
if (this.quotePageDiscountCartItem) {
|
||||||
cartItems.push(this.servicePackageDiscountCartItem);
|
cartItems.push(this.quotePageDiscountCartItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.premiumAppointmentDiscountCartItem) {
|
if (this.premiumAppointmentDiscountCartItem) {
|
||||||
|
|
@ -500,12 +507,6 @@ export default {
|
||||||
return cartItems;
|
return cartItems;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
discountPackageNames() {
|
|
||||||
const discountServicePackage = experimentMixin.methods.getSettingValue(
|
|
||||||
experimentSettings.PROMO_ON_PACKAGE
|
|
||||||
);
|
|
||||||
return getDiscountedPackageName(discountServicePackage);
|
|
||||||
},
|
|
||||||
servicePackageTitleWidget() {
|
servicePackageTitleWidget() {
|
||||||
const servicePackageNames = this.getCmsContent(
|
const servicePackageNames = this.getCmsContent(
|
||||||
this.servicePackageOptionsCmsName,
|
this.servicePackageOptionsCmsName,
|
||||||
|
|
@ -550,8 +551,8 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
packagePrice += this.getVapsPrice(this.packageLevel);
|
packagePrice += this.getVapsPrice(this.packageLevel);
|
||||||
if (this.servicePackageDiscountCartItem) {
|
if (this.quotePageDiscountCartItem) {
|
||||||
packagePrice -= this.getServicePackageDiscount();
|
packagePrice -= this.getQuotePageDiscount();
|
||||||
}
|
}
|
||||||
|
|
||||||
return packagePrice;
|
return packagePrice;
|
||||||
|
|
@ -928,25 +929,25 @@ export default {
|
||||||
}
|
}
|
||||||
return cartItem;
|
return cartItem;
|
||||||
},
|
},
|
||||||
servicePackageDiscountCartItemName() {
|
quotePageDiscountCartItemName() {
|
||||||
return this.getCmsContent("ServicePackageDiscountTextWidget", "Text");
|
return this.getCmsContent("ServicePackageDiscountTextWidget", "Text");
|
||||||
},
|
},
|
||||||
servicePackageDiscountCartItem() {
|
quotePageDiscountCartItem() {
|
||||||
let cartItem = null;
|
let cartItem = null;
|
||||||
|
|
||||||
const servicePackageDiscountLineItem = this.supportingItems.find(
|
const quotePageDiscountLineItem = this.supportingItems.find(
|
||||||
(lineItem) => lineItem.partType == partTypeStrings.SERVICE_PACKAGE_DISCOUNT
|
(lineItem) => lineItem.partType == partTypeStrings.QUOTE_PAGE_DISCOUNT
|
||||||
);
|
);
|
||||||
|
|
||||||
if (servicePackageDiscountLineItem) {
|
if (quotePageDiscountLineItem) {
|
||||||
cartItem = {
|
cartItem = {
|
||||||
name:
|
name:
|
||||||
this.servicePackageDiscountCartItemName +
|
this.quotePageDiscountCartItemName +
|
||||||
Math.abs(
|
Math.abs(
|
||||||
baseMixin.methods.getTotalLineItemPrice(servicePackageDiscountLineItem)
|
baseMixin.methods.getTotalLineItemPrice(quotePageDiscountLineItem)
|
||||||
),
|
),
|
||||||
category: cartItemCategories.SERVICE_PACKAGE_DISCOUNT,
|
category: cartItemCategories.QUOTE_PAGE_DISCOUNT,
|
||||||
cartItemType: cartItemTypes.SERVICE_PACKAGE_DISCOUNT,
|
cartItemType: cartItemTypes.QUOTE_PAGE_DISCOUNT,
|
||||||
isDisplayed: true,
|
isDisplayed: true,
|
||||||
isRemovable: false,
|
isRemovable: false,
|
||||||
subTotal: 0,
|
subTotal: 0,
|
||||||
|
|
@ -955,15 +956,15 @@ export default {
|
||||||
isCoveredByInsurance: false,
|
isCoveredByInsurance: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
servicePackageDiscountLineItem.cartItemType = cartItem.cartItemType;
|
quotePageDiscountLineItem.cartItemType = cartItem.cartItemType;
|
||||||
cartItem.lineItems.push(servicePackageDiscountLineItem);
|
cartItem.lineItems.push(quotePageDiscountLineItem);
|
||||||
|
|
||||||
cartItem.subTotal +=
|
cartItem.subTotal +=
|
||||||
(servicePackageDiscountLineItem.kitPrice ?? 0) +
|
(quotePageDiscountLineItem.kitPrice ?? 0) +
|
||||||
(servicePackageDiscountLineItem.laborAmount ?? 0) +
|
(quotePageDiscountLineItem.laborAmount ?? 0) +
|
||||||
(servicePackageDiscountLineItem.sellingPrice ?? 0);
|
(quotePageDiscountLineItem.sellingPrice ?? 0);
|
||||||
|
|
||||||
cartItem.salesTax += servicePackageDiscountLineItem.salesTax ?? 0;
|
cartItem.salesTax += quotePageDiscountLineItem.salesTax ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cartItem;
|
return cartItem;
|
||||||
|
|
@ -972,7 +973,7 @@ export default {
|
||||||
let cartItem = null;
|
let cartItem = null;
|
||||||
|
|
||||||
const otherSupportingItems = this.supportingItems.filter((item) => {
|
const otherSupportingItems = this.supportingItems.filter((item) => {
|
||||||
return item.partType != partTypeStrings.SERVICE_PACKAGE_DISCOUNT;
|
return item.partType != partTypeStrings.QUOTE_PAGE_DISCOUNT;
|
||||||
});
|
});
|
||||||
// Don't include fees they are part of the package total
|
// Don't include fees they are part of the package total
|
||||||
let otherSupportingItemsLineItems =
|
let otherSupportingItemsLineItems =
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="afterpay-banner" role="alert">
|
<div class="afterpay-modal-banner" role="alert">
|
||||||
<component
|
|
||||||
:is="'script'"
|
|
||||||
src="https://js.squarecdn.com/square-marketplace.js"
|
|
||||||
async></component>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span
|
<span
|
||||||
class="callout"
|
class="callout"
|
||||||
|
|
@ -22,22 +17,52 @@
|
||||||
<span v-else v-html="token"></span>
|
<span v-else v-html="token"></span>
|
||||||
<span class="nbsp"> </span>
|
<span class="nbsp"> </span>
|
||||||
</span>
|
</span>
|
||||||
<a
|
<textLink
|
||||||
id="afterpay-learnmore"
|
linkType="text"
|
||||||
href="#"
|
:text="bannerCta"
|
||||||
data-afterpay-modal="en_US"
|
href="javascript:void(0)"
|
||||||
data-bind="click:afterpayLearnMore">
|
@click-event="openModal" />
|
||||||
{{ modalCopy }}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<modal
|
||||||
|
:ref="modalName"
|
||||||
|
class="afterpay-modal"
|
||||||
|
:headerText="modalHeaderText"
|
||||||
|
:isFooterButtonSuppressed="true">
|
||||||
|
<template v-slot:modal-header-slot>
|
||||||
|
<span>{{ modalSubHeaderText }}</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot>
|
||||||
|
<span class="afterpay-sections">
|
||||||
|
<span
|
||||||
|
v-for="section in afterpaySectionsContent"
|
||||||
|
:key="section"
|
||||||
|
class="afterpay-section">
|
||||||
|
<span class="section-header">
|
||||||
|
<img v-if="section.AnswerImageUrl" :src="section.AnswerImageUrl" />
|
||||||
|
<h3 v-if="section.Text" v-html="section.Text"></h3>
|
||||||
|
</span>
|
||||||
|
<span v-html="section.content"></span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:modal-footer-slot>
|
||||||
|
<p class="modal-disclaimer" v-html="modalDisclaimerText"></p>
|
||||||
|
</template>
|
||||||
|
</modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { splitCopyOnCMSPlaceHolder, splitCMSCopyOnBR } from "@/helpers/cms-content-helper";
|
import {
|
||||||
import baseMixin from "@/mixins/base-mixin.js";
|
doesCopyContainTextLink,
|
||||||
import { getPromosThatMatchLineItemsOnOrder } from "@/helpers/promotions-helper";
|
splitCopyOnCMSPlaceHolder,
|
||||||
import { getArrayOfAllLineItemsAndChildParts } from "@/store";
|
getRouterLinkRouteFromCopy,
|
||||||
|
getRouterLinkDisplayTextFromCopy,
|
||||||
|
getExternalLink,
|
||||||
|
splitCMSCopyOnBR,
|
||||||
|
} from "@/helpers/cms-content-helper";
|
||||||
|
import textLink from "@/ux-components/text-link/text-link";
|
||||||
|
import modal from "@/digital-components/modal/modal";
|
||||||
|
|
||||||
const INLINE_IMAGE_TOKEN = "custom:inlineImage";
|
const INLINE_IMAGE_TOKEN = "custom:inlineImage";
|
||||||
const AFTERPAY_PRICE_TOKEN = "custom:afterpayPrice";
|
const AFTERPAY_PRICE_TOKEN = "custom:afterpayPrice";
|
||||||
|
|
@ -48,20 +73,16 @@ function isInlineImageToken(token) {
|
||||||
function isAfterpayPriceToken(token) {
|
function isAfterpayPriceToken(token) {
|
||||||
return token.includes(AFTERPAY_PRICE_TOKEN);
|
return token.includes(AFTERPAY_PRICE_TOKEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInlineAltText(token) {
|
function getInlineAltText(token) {
|
||||||
const innerTokens = token.split(",");
|
const innerTokens = token.split(",");
|
||||||
|
|
||||||
return innerTokens[1] ?? "";
|
return innerTokens[1] ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "afterpay-modal-banner",
|
name: "afterpay-banner",
|
||||||
props: {
|
props: {
|
||||||
cmsWidgetName: String,
|
cmsWidgetName: String,
|
||||||
lineItems: Array,
|
modalWidgetName: String,
|
||||||
isInsuranceSelected: Boolean,
|
|
||||||
afterpayExtendedPayOptionThreshold: Number,
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {};
|
||||||
|
|
@ -70,6 +91,17 @@ export default {
|
||||||
isInlineImageToken,
|
isInlineImageToken,
|
||||||
isAfterpayPriceToken,
|
isAfterpayPriceToken,
|
||||||
getInlineAltText,
|
getInlineAltText,
|
||||||
|
doesCopyContainTextLink,
|
||||||
|
splitCopyOnCMSPlaceHolder,
|
||||||
|
getRouterLinkRouteFromCopy,
|
||||||
|
getRouterLinkDisplayTextFromCopy,
|
||||||
|
getExternalLink,
|
||||||
|
openModal() {
|
||||||
|
this.modal?.openModal();
|
||||||
|
},
|
||||||
|
closeModal() {
|
||||||
|
this.modal?.closeModal();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
imageUrl() {
|
imageUrl() {
|
||||||
|
|
@ -84,39 +116,40 @@ export default {
|
||||||
afterpayCopyTokens() {
|
afterpayCopyTokens() {
|
||||||
return splitCopyOnCMSPlaceHolder(this.getCmsContent(this.cmsWidgetName, "BodyText"));
|
return splitCopyOnCMSPlaceHolder(this.getCmsContent(this.cmsWidgetName, "BodyText"));
|
||||||
},
|
},
|
||||||
modalCopy() {
|
bannerCta() {
|
||||||
return this.getCmsContent(this.cmsWidgetName, "FooterText");
|
return this.getCmsContent(this.cmsWidgetName, "FooterText");
|
||||||
},
|
},
|
||||||
getTierOnePackagePrice() {
|
modalHeaderText() {
|
||||||
let allLineItems = getArrayOfAllLineItemsAndChildParts(this.lineItems);
|
return this.getCmsContent(this.modalWidgetName, "HeaderText");
|
||||||
if (this.lineItems.promos) {
|
},
|
||||||
allLineItems = allLineItems?.filter((item) => item.partType !== "PROMO_DISCOUNT");
|
modalDisclaimerText() {
|
||||||
}
|
return this.getCmsContent(this.modalWidgetName, "BodyText2");
|
||||||
|
},
|
||||||
let price = baseMixin.methods.getTierOnePackagePrice(
|
modalName() {
|
||||||
baseMixin.methods.filterOutFees(allLineItems)
|
return this.modalWidgetName;
|
||||||
);
|
},
|
||||||
|
modal() {
|
||||||
if (this.lineItems.promos) {
|
return this.$refs[this.modalName];
|
||||||
let allLineItems = getArrayOfAllLineItemsAndChildParts(this.lineItems);
|
},
|
||||||
const promos = getPromosThatMatchLineItemsOnOrder(
|
afterpaySectionsContent() {
|
||||||
this.lineItems.promos,
|
const sections = this.getCmsContent("AfterpayModalSectionsWidget", "Answers");
|
||||||
allLineItems
|
if (!sections) return [];
|
||||||
);
|
sections.forEach((section) => {
|
||||||
promos.forEach((promo) => {
|
const sectionContent = this.getCmsContent(section.Name, "BodyText");
|
||||||
price += baseMixin.methods.getTotalLineItemPrice(promo);
|
section["content"] = sectionContent;
|
||||||
});
|
});
|
||||||
}
|
return sections;
|
||||||
|
|
||||||
return price;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {},
|
components: {
|
||||||
|
textLink,
|
||||||
|
modal,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
#afterpay-banner {
|
.afterpay-modal-banner {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: $blue-150;
|
background-color: $blue-150;
|
||||||
|
|
@ -166,12 +199,116 @@ export default {
|
||||||
max-width: 32rem;
|
max-width: 32rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
& .alert-heading {
|
}
|
||||||
font-size: 0.875rem;
|
|
||||||
|
.afterpay-modal {
|
||||||
|
&.modal.modal-component {
|
||||||
|
:deep(.modal-dialog) {
|
||||||
|
margin: 0 1rem;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 840px;
|
||||||
|
top: 3.4rem;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
transform: none;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
.modal-content {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
|
||||||
|
.modal-header.mt-6 {
|
||||||
|
background-color: $blue-100;
|
||||||
|
margin-top: 0;
|
||||||
|
padding: 1.5rem 2rem 1.5rem 1.5rem;
|
||||||
|
font-family: UrbanistSemibold;
|
||||||
|
|
||||||
|
.modal-title.justify-content-center {
|
||||||
|
// OVERRIDE
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
.btn-close {
|
||||||
|
top: 1.25rem;
|
||||||
|
right: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.afterpay-section {
|
||||||
|
display: block;
|
||||||
|
border: 1px solid $gray-200;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-family: UrbanistSemibold;
|
||||||
|
font-size: 1rem;
|
||||||
|
margin: 0 0 0 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
strong {
|
||||||
|
font-family: UrbanistSemibold;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 1.5rem;
|
||||||
|
|
||||||
|
.afterpay-sections {
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
display: flex;
|
||||||
|
column-gap: 1rem;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.afterpay-section {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
flex: 0 1 33%;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.modal-dialog-centered {
|
||||||
|
height: auto;
|
||||||
|
min-height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.modal-footer) {
|
||||||
|
position: relative;
|
||||||
|
background: $gray-100;
|
||||||
|
|
||||||
|
.modal-disclaimer {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
a {
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1.625;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#afterpay-learnmore {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ jest.mock("@/mixins/base-mixin", () => ({
|
||||||
filterOutFees(items) {
|
filterOutFees(items) {
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
filterOutServicePackageDiscountPart(items) {
|
filterOutQuotePageDiscountPart(items) {
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
isFormValid(form) {
|
isFormValid(form) {
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,11 @@
|
||||||
:isRecalibrationOnOrder="isRecalibrationOnOrder"
|
:isRecalibrationOnOrder="isRecalibrationOnOrder"
|
||||||
:shouldHideRecalibration="shouldHideRecalibration"
|
:shouldHideRecalibration="shouldHideRecalibration"
|
||||||
@vapsItemsSelected="vapsItemsSelectedAction"
|
@vapsItemsSelected="vapsItemsSelectedAction"
|
||||||
@servicePackageDiscountSelected="servicePackageDiscountSelectedAction"
|
@quotePageDiscountSelected="quotePageDiscountSelectedAction"
|
||||||
@currentNumberOfPackages="currentNumberOfPackagesAction"
|
@currentNumberOfPackages="currentNumberOfPackagesAction"
|
||||||
:servicePackage="servicePackage"
|
:servicePackage="servicePackage"
|
||||||
:activePromos="lineItems.promos"
|
:activePromos="lineItems.promos"
|
||||||
|
:availableQuotePageDiscounts="quoteDiscountPartsInfo"
|
||||||
v-on="{ 'buttonEvent.openModal': openModalAction }"
|
v-on="{ 'buttonEvent.openModal': openModalAction }"
|
||||||
validationRules="option-required"
|
validationRules="option-required"
|
||||||
isRequired />
|
isRequired />
|
||||||
|
|
@ -45,10 +46,8 @@
|
||||||
<!-- If Cash AND vehicle does not require recal OR if Cash AND State requires showing recal, then show Afterpay banner -->
|
<!-- If Cash AND vehicle does not require recal OR if Cash AND State requires showing recal, then show Afterpay banner -->
|
||||||
<afterpayModalBanner
|
<afterpayModalBanner
|
||||||
v-if="showAfterpayBanner"
|
v-if="showAfterpayBanner"
|
||||||
cmsWidgetName="AfterpayModalWidget"
|
cmsWidgetName="AfterpayBannerWidget"
|
||||||
:afterpayExtendedPayOptionThreshold="afterpayExtendedPayOptionThreshold"
|
modalWidgetName="AfterpayModalWidget" />
|
||||||
:isInsuranceSelected="isInsuranceSelected"
|
|
||||||
:lineItems="lineItems" />
|
|
||||||
|
|
||||||
<!-- If vehicle requires recal AND we are hiding recal pricing info, then show recal disclaimer banner. -->
|
<!-- If vehicle requires recal AND we are hiding recal pricing info, then show recal disclaimer banner. -->
|
||||||
<recalDisclaimer
|
<recalDisclaimer
|
||||||
|
|
@ -176,6 +175,7 @@ import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stas
|
||||||
import { storeMutations } from "@/constants/store-mutations";
|
import { storeMutations } from "@/constants/store-mutations";
|
||||||
import { debugLog } from "@/helpers/debug-log-helper";
|
import { debugLog } from "@/helpers/debug-log-helper";
|
||||||
import { savePageData } from "@/router/methods/helpers/save-page-data";
|
import { savePageData } from "@/router/methods/helpers/save-page-data";
|
||||||
|
import { quotePageDiscountTable } from "../../constants/quote-page-discounts";
|
||||||
|
|
||||||
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
|
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
|
||||||
|
|
||||||
|
|
@ -186,6 +186,14 @@ const ACCEPTED_QUOTE_TYPES = {
|
||||||
INSURANCE: "insurance",
|
INSURANCE: "insurance",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getAvailableQuotePageDiscounts() {
|
||||||
|
const activeDiscounts = quotePageDiscountTable?.filter((discountEntry) =>
|
||||||
|
experimentMixin?.methods?.hasSettingEqualTo(discountEntry?.experimentCode, "true")
|
||||||
|
);
|
||||||
|
|
||||||
|
return activeDiscounts ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "quote",
|
name: "quote",
|
||||||
async beforeRouteEnter(to, from, next) {
|
async beforeRouteEnter(to, from, next) {
|
||||||
|
|
@ -302,29 +310,22 @@ export default {
|
||||||
const lineItems = deepClone(store.getters.order.lineItems);
|
const lineItems = deepClone(store.getters.order.lineItems);
|
||||||
lineItems.vaps = lineItems.vaps ?? [];
|
lineItems.vaps = lineItems.vaps ?? [];
|
||||||
const nullSafeGlassParts = lineItems.glassParts ?? [];
|
const nullSafeGlassParts = lineItems.glassParts ?? [];
|
||||||
const servicePackageDiscountSettingValue = experimentMixin.methods.getSettingValue(
|
|
||||||
experimentSettings.SERVICE_PACKAGE_DISCOUNT
|
const quotePageDiscountPartsInfo = getAvailableQuotePageDiscounts();
|
||||||
);
|
|
||||||
const isServicePackageDiscount = servicePackageDiscountSettingValue === "True";
|
const quotePageDiscountParts = quotePageDiscountPartsInfo.map((partInfo) => ({
|
||||||
//Service package cash discount api call when experiment is active
|
partNumber: partInfo.partNumber,
|
||||||
var servicePackageDiscountPart = [];
|
partType: partInfo.partType,
|
||||||
if (isServicePackageDiscount) {
|
description: partInfo.description,
|
||||||
const servicePackageDiscountPartResponse =
|
isInsurable: null,
|
||||||
await baseMixin.methods.dispatchStoreActionWithLogging(
|
}));
|
||||||
storeActions.GET_SERVICE_PACKAGE_DISCOUNT_PART,
|
|
||||||
null,
|
|
||||||
"quote"
|
|
||||||
);
|
|
||||||
if (servicePackageDiscountPartResponse.data) {
|
|
||||||
servicePackageDiscountPart.push(servicePackageDiscountPartResponse.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const availableLineItems = [
|
const availableLineItems = [
|
||||||
resultMap.rainRepel,
|
resultMap.rainRepel,
|
||||||
...resultMap.supportingItems,
|
...resultMap.supportingItems,
|
||||||
...resultMap.wipers,
|
...resultMap.wipers,
|
||||||
...nullSafeGlassParts,
|
...nullSafeGlassParts,
|
||||||
...servicePackageDiscountPart,
|
...quotePageDiscountParts,
|
||||||
];
|
];
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -351,7 +352,7 @@ export default {
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
// This will remove any servicePackageDiscount item from lineItems.supportingItems
|
// This will remove any discount item from lineItems.supportingItems
|
||||||
baseMixin.methods.dispatchStoreAction(
|
baseMixin.methods.dispatchStoreAction(
|
||||||
storeActions.SAVE_SUPPORTING_ITEMS,
|
storeActions.SAVE_SUPPORTING_ITEMS,
|
||||||
resultMap.supportingItems,
|
resultMap.supportingItems,
|
||||||
|
|
@ -453,10 +454,9 @@ export default {
|
||||||
)
|
)
|
||||||
: baseMixin.methods.filterOutFees(availableLineItems);
|
: baseMixin.methods.filterOutFees(availableLineItems);
|
||||||
|
|
||||||
const lineItemsNoDiscount =
|
const lineItemsNoDiscount = baseMixin.methods.filterOutQuotePageDiscountPart(
|
||||||
baseMixin.methods.filterOutServicePackageDiscountPart(
|
lineItemsForCalculatingPrice
|
||||||
lineItemsForCalculatingPrice
|
);
|
||||||
);
|
|
||||||
|
|
||||||
if (isInsuranceFromQueryString != null) {
|
if (isInsuranceFromQueryString != null) {
|
||||||
if (isInsuranceFromQueryString.toLowerCase() === "true") {
|
if (isInsuranceFromQueryString.toLowerCase() === "true") {
|
||||||
|
|
@ -580,17 +580,8 @@ export default {
|
||||||
isRecalibrationOnOrder() {
|
isRecalibrationOnOrder() {
|
||||||
return store.getters.isRecalibrationOnOrder;
|
return store.getters.isRecalibrationOnOrder;
|
||||||
},
|
},
|
||||||
isServicePackageDiscountOnOrder() {
|
isQuotePageDiscountOnOrder() {
|
||||||
return containsLineItemWithPartType(
|
return this.quotePageDiscountPartsInfo?.length > 0;
|
||||||
partTypeStrings.SERVICE_PACKAGE_DISCOUNT,
|
|
||||||
this.availableLineItems
|
|
||||||
);
|
|
||||||
},
|
|
||||||
servicePackageDiscountParts() {
|
|
||||||
return findLineItemsWithPartType(
|
|
||||||
partTypeStrings.SERVICE_PACKAGE_DISCOUNT,
|
|
||||||
this.availableLineItems
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
shouldHideRecalibration() {
|
shouldHideRecalibration() {
|
||||||
return (
|
return (
|
||||||
|
|
@ -619,6 +610,23 @@ export default {
|
||||||
thresholdAmount && (thresholdAmount = parseInt(thresholdAmount));
|
thresholdAmount && (thresholdAmount = parseInt(thresholdAmount));
|
||||||
return thresholdAmount;
|
return thresholdAmount;
|
||||||
},
|
},
|
||||||
|
quoteDiscountPartsInfo() {
|
||||||
|
const defs = getAvailableQuotePageDiscounts();
|
||||||
|
const withPrices = defs
|
||||||
|
.map((def) => {
|
||||||
|
const match = this.availableLineItems?.find?.(
|
||||||
|
(pricedItem) => pricedItem.partNumber === def.partNumber
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
packageLevel: def.packageLevel,
|
||||||
|
item: match,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((result) => result.item);
|
||||||
|
|
||||||
|
return withPrices;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getColCount() {
|
getColCount() {
|
||||||
|
|
@ -662,7 +670,7 @@ export default {
|
||||||
vapsItemsSelectedAction(vapsItemsSelected) {
|
vapsItemsSelectedAction(vapsItemsSelected) {
|
||||||
this.lineItems.vaps = vapsItemsSelected;
|
this.lineItems.vaps = vapsItemsSelected;
|
||||||
},
|
},
|
||||||
servicePackageDiscountSelectedAction(supportingItemsSelected) {
|
quotePageDiscountSelectedAction(supportingItemsSelected) {
|
||||||
this.lineItems.supportingItems = supportingItemsSelected;
|
this.lineItems.supportingItems = supportingItemsSelected;
|
||||||
},
|
},
|
||||||
backButtonAction() {
|
backButtonAction() {
|
||||||
|
|
@ -724,10 +732,10 @@ export default {
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (this.isInsuranceSelected && this.isServicePackageDiscountOnOrder) {
|
if (this.isInsuranceSelected && this.isQuotePageDiscountOnOrder) {
|
||||||
let supportingItems = this.lineItems.supportingItems;
|
let supportingItems = this.lineItems.supportingItems;
|
||||||
supportingItems = supportingItems.filter((item) => {
|
supportingItems = supportingItems.filter((item) => {
|
||||||
return item.partType != this.servicePackageDiscountParts[0].partType;
|
return item.partType != partTypeStrings.QUOTE_PAGE_DISCOUNT;
|
||||||
});
|
});
|
||||||
this.dispatchStoreAction(
|
this.dispatchStoreAction(
|
||||||
this.storeActions.SAVE_SUPPORTING_ITEMS,
|
this.storeActions.SAVE_SUPPORTING_ITEMS,
|
||||||
|
|
@ -813,19 +821,23 @@ export default {
|
||||||
lineItemsToPrice =
|
lineItemsToPrice =
|
||||||
baseMixin.methods.filterOutRecalibration(lineItemsToPrice);
|
baseMixin.methods.filterOutRecalibration(lineItemsToPrice);
|
||||||
}
|
}
|
||||||
if (this.isServicePackageDiscountOnOrder) {
|
if (this.isQuotePageDiscountOnOrder) {
|
||||||
lineItemsToPrice = lineItemsToPrice?.filter((item) => {
|
lineItemsToPrice = lineItemsToPrice?.filter((item) => {
|
||||||
return (
|
return item.partType != partTypeStrings.QUOTE_PAGE_DISCOUNT;
|
||||||
item.partType != this.servicePackageDiscountParts[0].partType
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let price = 0;
|
let price = 0;
|
||||||
if (
|
if (
|
||||||
tier.additionalButtonData?.servicePackageDiscount &&
|
tier.additionalButtonData?.hasDiscount &&
|
||||||
this.isServicePackageDiscountOnOrder
|
this.isQuotePageDiscountOnOrder
|
||||||
) {
|
) {
|
||||||
lineItemsToPrice.push(...this.servicePackageDiscountParts);
|
const parts = this.quoteDiscountPartsInfo;
|
||||||
|
const matchPart = parts.find(
|
||||||
|
(discountInfo) => discountInfo.packageLevel === tier
|
||||||
|
);
|
||||||
|
if (matchPart) {
|
||||||
|
lineItemsToPrice.push(matchPart);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
price += baseMixin.methods.getTierOnePackagePrice(
|
price += baseMixin.methods.getTierOnePackagePrice(
|
||||||
baseMixin.methods.filterOutFees(lineItemsToPrice)
|
baseMixin.methods.filterOutFees(lineItemsToPrice)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { experimentSettings } from "@/constants/experiments";
|
||||||
import baseMixin from "@/mixins/base-mixin.js";
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
import { partTypeStrings } from "@/constants/part-type-strings";
|
import { partTypeStrings } from "@/constants/part-type-strings";
|
||||||
import { nextTick } from "vue";
|
import { nextTick } from "vue";
|
||||||
|
import { packageNames } from "../../../constants/package-names";
|
||||||
|
|
||||||
jest.mock("@/store", () => ({
|
jest.mock("@/store", () => ({
|
||||||
commit: jest.fn(),
|
commit: jest.fn(),
|
||||||
|
|
@ -73,9 +74,9 @@ describe("service-package-question.vue", () => {
|
||||||
price: 35.5,
|
price: 35.5,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
partNumber: "DISC CASHSAVE70",
|
partNumber: "PRM RCL CSH PKG",
|
||||||
description: null,
|
description: null,
|
||||||
partType: "SERVICE PACKAGE DISCOUNT",
|
partType: "QUOTE PAGE DISCOUNT",
|
||||||
price: -70,
|
price: -70,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -105,13 +106,27 @@ describe("service-package-question.vue", () => {
|
||||||
description: null,
|
description: null,
|
||||||
kitPrice: 0,
|
kitPrice: 0,
|
||||||
laborAmount: 0,
|
laborAmount: 0,
|
||||||
partNumber: "DISC CASHSAVE70",
|
partNumber: "PRM RCL CSH PKG",
|
||||||
partType: "SERVICE PACKAGE DISCOUNT",
|
partType: "QUOTE PAGE DISCOUNT",
|
||||||
salesTax: null,
|
salesTax: null,
|
||||||
sellingPrice: -70,
|
sellingPrice: -70,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
availableQuotePageDiscounts: [
|
||||||
|
{
|
||||||
|
packageLevel: packageNames.TIER_THREE,
|
||||||
|
item: {
|
||||||
|
description: null,
|
||||||
|
kitPrice: 0,
|
||||||
|
laborAmount: 0,
|
||||||
|
partNumber: "PRM RCL CSH PKG",
|
||||||
|
partType: "QUOTE PAGE DISCOUNT",
|
||||||
|
salesTax: null,
|
||||||
|
sellingPrice: -70,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
activePromos: [],
|
activePromos: [],
|
||||||
};
|
};
|
||||||
const packageNameKey = "05_01_CSR_Quote_Standard_Repair";
|
const packageNameKey = "05_01_CSR_Quote_Standard_Repair";
|
||||||
|
|
@ -136,41 +151,17 @@ describe("service-package-question.vue", () => {
|
||||||
});
|
});
|
||||||
it("should return price when there is discount", () => {
|
it("should return price when there is discount", () => {
|
||||||
const wrapper = setupMocks({});
|
const wrapper = setupMocks({});
|
||||||
const partType = partTypeStrings.SERVICE_PACKAGE_DISCOUNT;
|
|
||||||
|
|
||||||
wrapper.vm.isServicePackageDiscountOnOrder = containsLineItemWithPartType(
|
const price = wrapper.vm.getDiscountPrice(packageNames.TIER_THREE);
|
||||||
partType,
|
|
||||||
mockProps.lineItems.supportingItems
|
|
||||||
);
|
|
||||||
const servicePackageDiscountLineItem = findLineItemsWithPartType(
|
|
||||||
partType,
|
|
||||||
mockProps.lineItems.supportingItems
|
|
||||||
);
|
|
||||||
wrapper.vm.getServicePackageDiscountPrice();
|
|
||||||
const price = baseMixin.methods.getTotalLineItemPrice(servicePackageDiscountLineItem[0]);
|
|
||||||
|
|
||||||
expect(Math.abs(price)).toEqual(70);
|
expect(Math.abs(price)).toEqual(70);
|
||||||
});
|
});
|
||||||
it("isServicePackageDiscountOnOrder returns true if it contains service package discount lineItems", () => {
|
it("isDiscountOnOrder returns true if it contains quote package discount lineItems", () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const wrapper = setupMocks({});
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(wrapper.vm.isServicePackageDiscountOnOrder).toBe(true);
|
expect(wrapper.vm.isDiscountOnOrder).toBe(true);
|
||||||
});
|
|
||||||
it("servicePackageDiscountParts should returns service package discount lineItems", () => {
|
|
||||||
// Arrange
|
|
||||||
const wrapper = setupMocks({});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(wrapper.vm.servicePackageDiscountParts).toEqual([
|
|
||||||
{
|
|
||||||
partNumber: "DISC CASHSAVE70",
|
|
||||||
description: null,
|
|
||||||
partType: "SERVICE PACKAGE DISCOUNT",
|
|
||||||
price: -70,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
it("should have the correct insurance pricing text when insurance is selected", () => {
|
it("should have the correct insurance pricing text when insurance is selected", () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ export default {
|
||||||
servicePackage: null,
|
servicePackage: null,
|
||||||
isRecalibrationOnOrder: Boolean,
|
isRecalibrationOnOrder: Boolean,
|
||||||
shouldHideRecalibration: Boolean,
|
shouldHideRecalibration: Boolean,
|
||||||
|
availableQuotePageDiscounts: null,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -85,27 +86,18 @@ export default {
|
||||||
selectedPackageName(newValue) {
|
selectedPackageName(newValue) {
|
||||||
const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue);
|
const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue);
|
||||||
this.$emit("vapsItemsSelected", VapsProductsInSelectedPackage);
|
this.$emit("vapsItemsSelected", VapsProductsInSelectedPackage);
|
||||||
const servicePackageDiscountPartInSelectedPackage =
|
const discountPartInSelectedPackage = this.getDiscountPartForSelectedPackage(newValue);
|
||||||
this.getServicePackageDiscountPartForSelectedPackage(newValue);
|
|
||||||
let supportingItems = this.supportingLineItems;
|
let supportingItems = this.supportingLineItems;
|
||||||
if (
|
|
||||||
containsLineItemWithPartType(
|
supportingItems = supportingItems.filter(
|
||||||
partTypeStrings.SERVICE_PACKAGE_DISCOUNT,
|
(item) => item.partType !== partTypeStrings.QUOTE_PAGE_DISCOUNT
|
||||||
supportingItems
|
);
|
||||||
)
|
|
||||||
) {
|
if (discountPartInSelectedPackage) {
|
||||||
supportingItems = supportingItems.filter(
|
supportingItems.push(discountPartInSelectedPackage);
|
||||||
(item) => item.partType !== partTypeStrings.SERVICE_PACKAGE_DISCOUNT
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
servicePackageDiscountPartInSelectedPackage?.length > 0 &&
|
|
||||||
supportingItems != null
|
|
||||||
) {
|
|
||||||
supportingItems.push(servicePackageDiscountPartInSelectedPackage[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit("servicePackageDiscountSelected", supportingItems);
|
this.$emit("quotePageDiscountSelected", supportingItems);
|
||||||
},
|
},
|
||||||
servicePackage(newValue) {
|
servicePackage(newValue) {
|
||||||
if (newValue !== null) {
|
if (newValue !== null) {
|
||||||
|
|
@ -118,7 +110,7 @@ export default {
|
||||||
return this.availableLineItems ?? [];
|
return this.availableLineItems ?? [];
|
||||||
},
|
},
|
||||||
supportingLineItems() {
|
supportingLineItems() {
|
||||||
return this.$store.getters.lineItems?.supportingItems;
|
return this.$store.getters.lineItems?.supportingItems ?? [];
|
||||||
},
|
},
|
||||||
servicePackageAnswers() {
|
servicePackageAnswers() {
|
||||||
const cmsWidgetName = this.isInsuranceSelected
|
const cmsWidgetName = this.isInsuranceSelected
|
||||||
|
|
@ -149,18 +141,15 @@ export default {
|
||||||
? this.getDiscountedPackagePriceString(answer.Name, false)
|
? this.getDiscountedPackagePriceString(answer.Name, false)
|
||||||
: this.getDiscountedPackagePriceString(
|
: this.getDiscountedPackagePriceString(
|
||||||
answer.Name,
|
answer.Name,
|
||||||
this.isServicePackageDiscountOnOrder &&
|
this.hasDiscountForPackage(answer.Name)
|
||||||
this.discountPackageNames == answer.Name
|
|
||||||
),
|
),
|
||||||
buttonFooterCopy: this.getFooterTextFromCms(answer.SubWidgetName),
|
buttonFooterCopy: this.getFooterTextFromCms(answer.SubWidgetName),
|
||||||
additionalButtonData: {
|
additionalButtonData: {
|
||||||
strikeThroughPrice: this.getPackagePriceString(answer.Name),
|
strikeThroughPrice: this.getPackagePriceString(answer.Name),
|
||||||
servicePackageDiscount:
|
hasDiscount: this.hasDiscountForPackage(answer.Name),
|
||||||
this.isServicePackageDiscountOnOrder &&
|
|
||||||
this.discountPackageNames == answer.Name,
|
|
||||||
Text: this.isInsuranceSelected
|
Text: this.isInsuranceSelected
|
||||||
? false
|
? false
|
||||||
: "Special: Save $" + this.getServicePackageDiscountPrice(),
|
: "Special: Save $" + this.getDiscountPrice(answer.Name),
|
||||||
isInsuranceSelected: this.isInsuranceSelected,
|
isInsuranceSelected: this.isInsuranceSelected,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
@ -168,17 +157,8 @@ export default {
|
||||||
this.$emit("currentNumberOfPackages", modifiedAnswers.length);
|
this.$emit("currentNumberOfPackages", modifiedAnswers.length);
|
||||||
return modifiedAnswers;
|
return modifiedAnswers;
|
||||||
},
|
},
|
||||||
isServicePackageDiscountOnOrder() {
|
isDiscountOnOrder() {
|
||||||
return containsLineItemWithPartType(
|
return this.availableQuotePageDiscounts?.length > 0;
|
||||||
partTypeStrings.SERVICE_PACKAGE_DISCOUNT,
|
|
||||||
this.nullSafeAvailableLineItems
|
|
||||||
);
|
|
||||||
},
|
|
||||||
discountPackageNames() {
|
|
||||||
const discountServicePackage = experimentMixin.methods.getSettingValue(
|
|
||||||
experimentSettings.PROMO_ON_PACKAGE
|
|
||||||
);
|
|
||||||
return getDiscountedPackageName(discountServicePackage);
|
|
||||||
},
|
},
|
||||||
frontWipersApplicableForTierTwo() {
|
frontWipersApplicableForTierTwo() {
|
||||||
return shouldFrontWipersBeAvailable(
|
return shouldFrontWipersBeAvailable(
|
||||||
|
|
@ -226,12 +206,6 @@ export default {
|
||||||
isRepair() {
|
isRepair() {
|
||||||
return this.$store.getters.order.damage.isRepair;
|
return this.$store.getters.order.damage.isRepair;
|
||||||
},
|
},
|
||||||
servicePackageDiscountParts() {
|
|
||||||
return findLineItemsWithPartType(
|
|
||||||
partTypeStrings.SERVICE_PACKAGE_DISCOUNT,
|
|
||||||
this.nullSafeAvailableLineItems
|
|
||||||
);
|
|
||||||
},
|
|
||||||
showRecalInServicePackages() {
|
showRecalInServicePackages() {
|
||||||
return this.isRecalibrationOnOrder && !this.shouldHideRecalibration;
|
return this.isRecalibrationOnOrder && !this.shouldHideRecalibration;
|
||||||
},
|
},
|
||||||
|
|
@ -280,16 +254,16 @@ export default {
|
||||||
const formattedPriceFloat = parseFloat(
|
const formattedPriceFloat = parseFloat(
|
||||||
this.getPackagePrice(packageName, {
|
this.getPackagePrice(packageName, {
|
||||||
discountedPrice: false,
|
discountedPrice: false,
|
||||||
servicePackageDiscount: false,
|
quotePageDiscount: false,
|
||||||
})
|
})
|
||||||
).toFixed(2);
|
).toFixed(2);
|
||||||
return "$" + formattedPriceFloat;
|
return "$" + formattedPriceFloat;
|
||||||
},
|
},
|
||||||
getDiscountedPackagePriceString(packageName, servicePackageDiscount) {
|
getDiscountedPackagePriceString(packageName, quotePageDiscount) {
|
||||||
const formattedPriceFloat = parseFloat(
|
const formattedPriceFloat = parseFloat(
|
||||||
this.getPackagePrice(packageName, {
|
this.getPackagePrice(packageName, {
|
||||||
discountedPrice: true,
|
discountedPrice: true,
|
||||||
servicePackageDiscount,
|
quotePageDiscount,
|
||||||
})
|
})
|
||||||
).toFixed(2);
|
).toFixed(2);
|
||||||
|
|
||||||
|
|
@ -298,7 +272,7 @@ export default {
|
||||||
}
|
}
|
||||||
return (this.isInsuranceSelected ? "As little as $" : "$") + formattedPriceFloat;
|
return (this.isInsuranceSelected ? "As little as $" : "$") + formattedPriceFloat;
|
||||||
},
|
},
|
||||||
getPackagePrice(packageName, { discountedPrice = false, servicePackageDiscount = false }) {
|
getPackagePrice(packageName, { discountedPrice = false, quotePageDiscount = false }) {
|
||||||
let lineItemsToPrice = [...this.nullSafeAvailableLineItems];
|
let lineItemsToPrice = [...this.nullSafeAvailableLineItems];
|
||||||
|
|
||||||
if (this.isRecalibrationOnOrder && this.shouldHideRecalibration) {
|
if (this.isRecalibrationOnOrder && this.shouldHideRecalibration) {
|
||||||
|
|
@ -306,17 +280,20 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
//remove service package discount part
|
//remove service package discount part
|
||||||
if (this.isServicePackageDiscountOnOrder) {
|
if (this.isDiscountOnOrder) {
|
||||||
lineItemsToPrice = lineItemsToPrice.filter((item) => {
|
lineItemsToPrice = lineItemsToPrice.filter((item) => {
|
||||||
return item.partType != this.servicePackageDiscountParts[0].partType;
|
return item.partType != partTypeStrings.QUOTE_PAGE_DISCOUNT;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (discountedPrice) {
|
if (discountedPrice) {
|
||||||
lineItemsToPrice.push(...removeVapsPromosFromPromoArray(this.activePromos));
|
lineItemsToPrice.push(...removeVapsPromosFromPromoArray(this.activePromos));
|
||||||
}
|
}
|
||||||
if (servicePackageDiscount) {
|
if (quotePageDiscount) {
|
||||||
lineItemsToPrice.push(...this.servicePackageDiscountParts);
|
const part = this.getDiscountPartForSelectedPackage(packageName);
|
||||||
|
if (part) {
|
||||||
|
lineItemsToPrice.push(part);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let priceFloat = this.isInsuranceSelected
|
let priceFloat = this.isInsuranceSelected
|
||||||
? 0
|
? 0
|
||||||
|
|
@ -328,14 +305,16 @@ export default {
|
||||||
|
|
||||||
return priceFloat;
|
return priceFloat;
|
||||||
},
|
},
|
||||||
getServicePackageDiscountPrice() {
|
getDiscountPrice(packageName) {
|
||||||
if (this.isServicePackageDiscountOnOrder) {
|
const part = this.getDiscountPartForSelectedPackage(packageName);
|
||||||
let price = 0;
|
|
||||||
price += baseMixin.methods.getTotalLineItemPrice(
|
if (part) {
|
||||||
this.servicePackageDiscountParts[0]
|
const price = baseMixin.methods.getTotalLineItemPrice(part);
|
||||||
);
|
|
||||||
return Math.abs(price);
|
return Math.abs(price);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
getVapsPrice(packageName, applyPromoDiscounts = false) {
|
getVapsPrice(packageName, applyPromoDiscounts = false) {
|
||||||
const vapsItems = this.getVapsLineItemsForSelectedPackage(packageName);
|
const vapsItems = this.getVapsLineItemsForSelectedPackage(packageName);
|
||||||
|
|
@ -398,13 +377,18 @@ export default {
|
||||||
|
|
||||||
return vapsLineItemsForSelectedPackage;
|
return vapsLineItemsForSelectedPackage;
|
||||||
},
|
},
|
||||||
getServicePackageDiscountPartForSelectedPackage(packageName) {
|
getDiscountPartForSelectedPackage(packageName) {
|
||||||
const selectedPackage = this.servicePackageAnswers.filter(
|
if (this.availableQuotePageDiscounts?.length > 0) {
|
||||||
(item) => item.value === packageName
|
const match = this.availableQuotePageDiscounts.find(
|
||||||
);
|
(discountInfo) => discountInfo.packageLevel === packageName
|
||||||
if (selectedPackage?.[0]?.additionalButtonData?.servicePackageDiscount) {
|
);
|
||||||
return this.servicePackageDiscountParts;
|
return match?.item;
|
||||||
} else return [];
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
hasDiscountForPackage(packageName) {
|
||||||
|
return !!this.getDiscountPartForSelectedPackage(packageName);
|
||||||
},
|
},
|
||||||
combineLineItemsWithoutDuplicates(lineItemsOne, lineItemsTwo) {
|
combineLineItemsWithoutDuplicates(lineItemsOne, lineItemsTwo) {
|
||||||
const combinedLineItemArray = [...lineItemsTwo];
|
const combinedLineItemArray = [...lineItemsTwo];
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
:class="[this.buttonLabelSubCopy ? 'has-subheader' : '']"
|
:class="[this.buttonLabelSubCopy ? 'has-subheader' : '']"
|
||||||
for="testradio">
|
for="testradio">
|
||||||
<div class="package-specs">
|
<div class="package-specs">
|
||||||
<div v-if="this.additionalButtonData.servicePackageDiscount" class="row">
|
<div v-if="this.additionalButtonData.hasDiscount" class="row">
|
||||||
<div class="col md-6">
|
<div class="col md-6">
|
||||||
<p class="m-0">
|
<p class="m-0">
|
||||||
<span v-html="this.buttonLabel"></span>
|
<span v-html="this.buttonLabel"></span>
|
||||||
|
|
@ -77,10 +77,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="this.additionalButtonData.hasDiscount && this.additionalButtonData.Text"
|
||||||
this.additionalButtonData.servicePackageDiscount &&
|
|
||||||
this.additionalButtonData.Text
|
|
||||||
"
|
|
||||||
class="special-save-box">
|
class="special-save-box">
|
||||||
<span v-html="this.additionalButtonData.Text"></span>
|
<span v-html="this.additionalButtonData.Text"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -129,9 +129,9 @@ export default {
|
||||||
});
|
});
|
||||||
return lineItemsArray;
|
return lineItemsArray;
|
||||||
},
|
},
|
||||||
filterOutServicePackageDiscountPart(lineItems) {
|
filterOutQuotePageDiscountPart(lineItems) {
|
||||||
const filteredLineItems = lineItems?.filter((item) => {
|
const filteredLineItems = lineItems?.filter((item) => {
|
||||||
return !item?.partType?.includes(partTypeStrings.SERVICE_PACKAGE_DISCOUNT);
|
return !item?.partType?.includes(partTypeStrings.QUOTE_PAGE_DISCOUNT);
|
||||||
});
|
});
|
||||||
return filteredLineItems;
|
return filteredLineItems;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,33 @@ import { queryStrings } from "@/constants/query-strings";
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
import { storeMutations } from "@/constants/store-mutations";
|
import { storeMutations } from "@/constants/store-mutations";
|
||||||
import { updateOrCreateFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
|
import { updateOrCreateFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
|
||||||
|
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
|
||||||
|
|
||||||
export async function consumeReferralQuerystrings() {
|
export async function consumeReferralQuerystrings() {
|
||||||
const referralNumber = getQuerystringParameter(queryStrings.REFERRAL_NUMBER);
|
var referralNumber = getQuerystringParameter(queryStrings.REFERRAL_NUMBER);
|
||||||
const parentAccount = getQuerystringParameter(queryStrings.PARENT_ACCOUNT);
|
var parentAccount = getQuerystringParameter(queryStrings.PARENT_ACCOUNT);
|
||||||
const correlationId = getQuerystringParameter(queryStrings.CORRELATION_ID);
|
var correlationId = getQuerystringParameter(queryStrings.CORRELATION_ID);
|
||||||
|
var referralDate = null;
|
||||||
|
|
||||||
|
// If no referral number in querystring, check if heritage funnel updated last.
|
||||||
|
// This would indicate a customer that went to heritage but did not return through the
|
||||||
|
// normal route. ie: left the site in search of a discount and returned without querystrings.
|
||||||
|
const didHeritageFunnelUpdateLast = getFunnelCookie()?.DidHeritageFunnelUpdateLast;
|
||||||
|
if (didHeritageFunnelUpdateLast && !referralNumber) {
|
||||||
|
referralNumber = getFunnelCookie().ReferralNumber;
|
||||||
|
parentAccount = getFunnelCookie().ReferralParentAccountNumber;
|
||||||
|
correlationId = getFunnelCookie().ReferralCorrelationId;
|
||||||
|
referralDate = getFunnelCookie().ReferralDate;
|
||||||
|
}
|
||||||
|
|
||||||
if (referralNumber) {
|
if (referralNumber) {
|
||||||
store.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber);
|
store.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber);
|
||||||
store.commit(storeMutations.UPDATE_PARENT_ACCT_NUMBER, parentAccount);
|
store.commit(storeMutations.UPDATE_PARENT_ACCT_NUMBER, parentAccount);
|
||||||
store.commit(storeMutations.UPDATE_REFERRAL_CORRELATION_ID, correlationId);
|
store.commit(storeMutations.UPDATE_REFERRAL_CORRELATION_ID, correlationId);
|
||||||
|
|
||||||
|
if (referralDate) {
|
||||||
|
store.commit(storeMutations.UPDATE_REFERRAL_DATE, referralDate);
|
||||||
|
}
|
||||||
// log(" --update cookie");
|
// log(" --update cookie");
|
||||||
updateOrCreateFunnelCookie();
|
updateOrCreateFunnelCookie();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@ import { queryStrings } from "@/constants/query-strings";
|
||||||
import { consumeReferralQuerystrings } from "@/router/methods/helpers/consume-referral-info";
|
import { consumeReferralQuerystrings } from "@/router/methods/helpers/consume-referral-info";
|
||||||
import { loadSessionIfPresent } from "@/helpers/heritage-integration/order-helper";
|
import { loadSessionIfPresent } from "@/helpers/heritage-integration/order-helper";
|
||||||
import { routeData } from "@/router/constants/routes";
|
import { routeData } from "@/router/constants/routes";
|
||||||
|
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
|
||||||
|
|
||||||
export async function handleHeritageReturn(to, from) {
|
export async function handleHeritageReturn(to, from) {
|
||||||
const fromHeritageFlag = to.query[queryStrings.FROM_HERITAGE];
|
const fromHeritageFlag = to.query[queryStrings.FROM_HERITAGE];
|
||||||
|
const didHeritageFunnelUpdateLast = getFunnelCookie()?.DidHeritageFunnelUpdateLast;
|
||||||
|
|
||||||
if (fromHeritageFlag) {
|
if (fromHeritageFlag || didHeritageFunnelUpdateLast) {
|
||||||
// Get new referral info from querystring in case passed there.
|
// Get new referral info from querystring in case passed there.
|
||||||
await consumeReferralQuerystrings();
|
await consumeReferralQuerystrings();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -123,8 +123,8 @@ $body-color: $gray-600;
|
||||||
|
|
||||||
//Fonts
|
//Fonts
|
||||||
$font-family-sans-serif: UrbanistRegular, Arial, Helvetica, sans-serif;
|
$font-family-sans-serif: UrbanistRegular, Arial, Helvetica, sans-serif;
|
||||||
$font-family-monospace:
|
$font-family-monospace: UrbanistRegular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
|
||||||
UrbanistRegular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
monospace;
|
||||||
// stylelint-enable value-keyword-case
|
// stylelint-enable value-keyword-case
|
||||||
$font-family-base: $font-family-sans-serif;
|
$font-family-base: $font-family-sans-serif;
|
||||||
$font-family-code: $font-family-monospace;
|
$font-family-code: $font-family-monospace;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue