Merge branch 'develop' into feature/CASH-823

This commit is contained in:
AdamCaouetteSafelite 2025-06-19 12:22:04 -04:00
commit 7a2bd42034
11 changed files with 226 additions and 151 deletions

View file

@ -2,14 +2,18 @@
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */ /* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */ /* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */ /* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
@import url("https://fonts.googleapis.com/css2?family=Urbanist:ital,wght@0,100..900;1,100..900&display=swap");
body { body {
font-family: Roboto; font-family: Urbanist, Arial, Helvetica, sans-serif;
font-weight: 400; font-weight: 400;
color: #000; color: #000;
margin: 0; margin: 0;
background-color: white; background-color: white;
padding: 8px; padding: 8px;
} }
body h3 {
font-weight: 600;
}
body .validation-info { body .validation-info {
display: none; display: none;
} }
@ -31,7 +35,7 @@ body .form-group {
body .headerlinecontainer .headerline1 { body .headerlinecontainer .headerline1 {
font-size: 20px; font-size: 20px;
line-height: 32px; line-height: 32px;
font-weight: 400; font-weight: 500;
margin: 24px 0; margin: 24px 0;
text-align: center; text-align: center;
} }
@ -92,7 +96,7 @@ body .creditCardSpecific div .headerlinecontainer {
display: none; display: none;
} }
body .creditCardSpecific div label { body .creditCardSpecific div label {
font-weight: 500; font-weight: 600;
} }
body .creditCardSpecific #infoRow2 { body .creditCardSpecific #infoRow2 {
margin-bottom: 0; margin-bottom: 0;
@ -268,6 +272,7 @@ body .cc-error-container #alert-message {
#fmg-checkout-shared #other-payments-container label span.paymentOptionText { #fmg-checkout-shared #other-payments-container label span.paymentOptionText {
margin: auto 0; margin: auto 0;
padding-left: 0; padding-left: 0;
font-weight: 600;
} }
#fmg-checkout-shared #other-payments-container #afterpayParentLink label > span:first-of-type { #fmg-checkout-shared #other-payments-container #afterpayParentLink label > span:first-of-type {
width: 100%; width: 100%;

View file

@ -2,14 +2,19 @@
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */ /* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */ /* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */ /* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
@import url("https://fonts.googleapis.com/css2?family=Urbanist:ital,wght@0,100..900;1,100..900&display=swap");
body { body {
font-family: Roboto; font-family: Urbanist, Arial, Helvetica, sans-serif;
font-weight: 400; font-weight: 400;
color: #000; color: #000;
margin: 0; margin: 0;
background-color: white; background-color: white;
padding: 8px; padding: 8px;
h3 {
font-weight: 600;
}
.validation-info { .validation-info {
display: none; display: none;
} }
@ -37,7 +42,7 @@ body {
.headerline1 { .headerline1 {
font-size: 20px; font-size: 20px;
line-height: 32px; line-height: 32px;
font-weight: 400; font-weight: 500;
margin: 24px 0; margin: 24px 0;
text-align: center; text-align: center;
} }
@ -98,7 +103,7 @@ body {
display: none; display: none;
} }
label { label {
font-weight: 500; font-weight: 600;
} }
} }
#infoRow2 { #infoRow2 {
@ -325,6 +330,7 @@ body {
&.paymentOptionText { &.paymentOptionText {
margin: auto 0; margin: auto 0;
padding-left: 0; padding-left: 0;
font-weight: 600;
} }
} }
} }

View file

@ -94,8 +94,16 @@ export default {
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, // for now passing in chat_summery in Subject. But this will be changed in future. 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 ( if (
window.embedded_svc.liveAgentAPI && window.embedded_svc.liveAgentAPI &&
typeof window.embedded_svc.liveAgentAPI.startChat === "function" typeof window.embedded_svc.liveAgentAPI.startChat === "function"

View file

@ -117,6 +117,10 @@
<span>{{ amountPaidText }}</span> <span>{{ amountPaidText }}</span>
<span>{{ getLineItemAmount(amountPaid) }}</span> <span>{{ getLineItemAmount(amountPaid) }}</span>
</div> </div>
<div v-if="donationCartItem" class="donation-amount">
<span>{{ donationCartItemName }}</span>
<span>{{ getLineItemAmount(donationCartItem.subTotal) }}</span>
</div>
<div class="amount-due"> <div class="amount-due">
<span>{{ amountDueText }}</span> <span>{{ amountDueText }}</span>
<span>{{ getLineItemAmount(amountDue, showCoverageAsPending) }}</span> <span>{{ getLineItemAmount(amountDue, showCoverageAsPending) }}</span>
@ -472,10 +476,6 @@ export default {
}); });
} }
if (this.donationCartItem) {
cartItems.push(this.donationCartItem);
}
return cartItems; return cartItems;
}, },
}, },
@ -1309,12 +1309,15 @@ export default {
.sub-total, .sub-total,
.sales-tax, .sales-tax,
.amount-due, .amount-due,
.amount-paid { .amount-paid,
font-weight: 500; .donation-amount {
font-family:
UrbanistSemibold, AvertaSemibold; //Okay to remove AvertaSemibold after 2025.06.19 merge/release
color: $black; color: $black;
} }
.sub-total { .sub-total {
border-top: 1px solid $green; border-top: 1px solid $green;
background-color: $green-100;
} }
.service-type, .service-type,
.deductible { .deductible {

View file

@ -62,7 +62,7 @@ import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
import { queryStrings } from "@/constants/query-strings"; import { queryStrings } from "@/constants/query-strings";
import { nextTick } from "vue"; import { nextTick } from "vue";
import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stash"; import { peekQueryFromStash } from "@/router/methods/helpers/querystring-stash";
// Define Validation Rules // Define Validation Rules
defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED)); defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
@ -100,7 +100,7 @@ export default {
} }
const zip = const zip =
consumeQueryFromStash(queryStrings.ZIP_CODE) ?? peekQueryFromStash(queryStrings.ZIP_CODE) ??
store.getters.order.serviceLocation.zipCode; store.getters.order.serviceLocation.zipCode;
var vinByAddressPromise; var vinByAddressPromise;

View file

@ -616,8 +616,11 @@ export default {
// TODO - WILL NEED TO SAVE THE APPOINTMENT TYPE ALSO (AND ANYTHING ELSE THAT SERVICE-LOCATION DID TOO) // TODO - WILL NEED TO SAVE THE APPOINTMENT TYPE ALSO (AND ANYTHING ELSE THAT SERVICE-LOCATION DID TOO)
this.updateSupportingItems(); this.updateSupportingItems();
if (this.waitListRequested) { if (this.displayWaitList) {
var gaLabel = this.isMobileSelected ? "checked_mobile" : "checked_inshop"; var gaLabel = "";
const status = this.waitListRequested ? "checked" : "unchecked";
const type = store.getters.isMobileAppointment ? "mobile" : "inshop";
gaLabel = `${status}_${type}`;
const currentDate = new Date(); const currentDate = new Date();
const dateString = this.isMobileSelected const dateString = this.isMobileSelected

View file

@ -1,4 +1,5 @@
import { reactive } from "vue"; import { reactive } from "vue";
import { debugLog } from "@/helpers/debug-log-helper";
const queryStash = reactive({ const queryStash = reactive({
queries: [], queries: [],
@ -27,10 +28,18 @@ export function stashAllQueries(toRoute) {
} }
function getStashedQuery(key) { function getStashedQuery(key) {
debugLog(`Fetching querystring with key:`, key);
const match = queryStash.queries.find( const match = queryStash.queries.find(
(entry) => entry?.key?.toLowerCase() === key?.toLowerCase() (entry) => entry?.key?.toLowerCase() === key?.toLowerCase()
); );
if (match) {
debugLog(`Found result:`, match.value);
debugLog(`Already consumed:`, match.used);
} else {
debugLog(`Found no result`);
}
return match; return match;
} }

View file

@ -0,0 +1,14 @@
import store from "@/store";
import { storeActions } from "@/constants/store-actions";
import { paymentMethods } from "@/constants/payment-method-constants";
import { debugLog } from "@/helpers/debug-log-helper";
export async function paymentBeforeEnter(to, from) {
const piaType = store.getters.order?.payment?.piaType;
debugLog(`Entering payment with type =`, piaType);
if (piaType === paymentMethods.PAYPAL) {
debugLog(`Changing to payment type =`, paymentMethods.CREDIT_CARD);
await store.dispatch(storeActions.SAVE_PAYMENT_METHOD_CHOICE, paymentMethods.CREDIT_CARD);
}
}

View file

@ -11,6 +11,7 @@ import { autoRouteBeforeEnter } from "@/router/methods/route-logic/auto-route";
import { errorBeforeEnter } from "@/router/methods/route-logic/error"; import { errorBeforeEnter } from "@/router/methods/route-logic/error";
import { restartBeforeEnter } from "@/router/methods/route-logic/restart"; import { restartBeforeEnter } from "@/router/methods/route-logic/restart";
import { paymentMethodBeforeEnter } from "@/router/methods/route-logic/payment-method"; import { paymentMethodBeforeEnter } from "@/router/methods/route-logic/payment-method";
import { paymentBeforeEnter } from "@/router/methods/route-logic/payment";
export const routes = [ export const routes = [
// Non-virtual pages. // Non-virtual pages.
@ -32,7 +33,7 @@ export const routes = [
createRoute(routeData.SCHEDULE), createRoute(routeData.SCHEDULE),
createRoute(routeData.CUSTOMER_DETAILS), createRoute(routeData.CUSTOMER_DETAILS),
createRoute(routeData.PAYMENT_METHOD, paymentMethodBeforeEnter), createRoute(routeData.PAYMENT_METHOD, paymentMethodBeforeEnter),
createRoute(routeData.PAYMENT), createRoute(routeData.PAYMENT, paymentBeforeEnter),
createRoute(routeData.PAYMENT_PIA_RETURN), createRoute(routeData.PAYMENT_PIA_RETURN),
createRoute(routeData.CONFIRMATION), createRoute(routeData.CONFIRMATION),
createRoute(routeData.RETURN_USER), createRoute(routeData.RETURN_USER),

View file

@ -1072,33 +1072,6 @@ export const getters = {
}, },
}; };
function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) {
return (array ?? []).map((x) => x[propertyName]).filter((x) => x);
}
function getTimeSlotsAdditionalEventData(
provisionalTriggers,
zipCode,
firstAvailableAppointmentDateString,
shopAppointmentType
) {
var numberOfDays = null;
if (firstAvailableAppointmentDateString)
numberOfDays = getDateDifferenceInDays(
new Date().toISOString().split("T")[0],
firstAvailableAppointmentDateString
);
if (shopAppointmentType)
return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ShopAppointmentType:${shopAppointmentType},ProvisionalTriggers:${provisionalTriggers.join(
","
)}`;
else
return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ProvisionalTriggers:${provisionalTriggers.join(
","
)}`;
}
// Export Actions // Export Actions
export const actions = { export const actions = {
// Vehicle API Actions // Vehicle API Actions
@ -2059,20 +2032,31 @@ export const actions = {
}, },
}; };
return globalMethods.callHttpClient({ let hasCalled = timeSlotCallFlags.shop;
if (!hasCalled) {
timeSlotCallFlags.shop = true;
}
const options = {
method: endpoints.GetShopTimeSlots.method, method: endpoints.GetShopTimeSlots.method,
endpoint: endpoints.GetShopTimeSlots.url, endpoint: endpoints.GetShopTimeSlots.url,
payload: payload, payload: payload,
logApiCall: true, logApiCall: true,
pageNameToLog: pageNameToLog, pageNameToLog: pageNameToLog,
additionalSuccessEventDataHandler: (response) => };
// Only set handler if this is the very first call in this session
if (!hasCalled) {
options.additionalSuccessEventDataHandler = (response) =>
getTimeSlotsAdditionalEventData( getTimeSlotsAdditionalEventData(
response.data.provisionalTriggers, response.data.provisionalTriggers,
order.serviceLocation.zipCode, order.serviceLocation.zipCode,
response.data.days?.[0]?.date, response.data.days?.[0]?.date,
shopAppointmentType shopAppointmentType
), );
}); }
return globalMethods.callHttpClient(options);
}, },
getMobileTimeSlots(context, { payload: { startDate, endDate }, pageNameToLog }) { getMobileTimeSlots(context, { payload: { startDate, endDate }, pageNameToLog }) {
@ -2116,19 +2100,30 @@ export const actions = {
zipCode: order.serviceLocation.zipCode, zipCode: order.serviceLocation.zipCode,
}; };
return globalMethods.callHttpClient({ let hasCalled = timeSlotCallFlags.mobile;
if (!hasCalled) {
timeSlotCallFlags.mobile = true;
}
const options = {
method: endpoints.GetMobileTimeSlots.method, method: endpoints.GetMobileTimeSlots.method,
endpoint: endpoints.GetMobileTimeSlots.url, endpoint: endpoints.GetMobileTimeSlots.url,
payload: payload, payload: payload,
logApiCall: true, logApiCall: true,
pageNameToLog: pageNameToLog, pageNameToLog: pageNameToLog,
additionalSuccessEventDataHandler: (response) => };
// Only set handler if this is the very first call in this session
if (!hasCalled) {
options.additionalSuccessEventDataHandler = (response) =>
getTimeSlotsAdditionalEventData( getTimeSlotsAdditionalEventData(
response.data.provisionalTriggers, response.data.provisionalTriggers,
order.serviceLocation.zipCode, order.serviceLocation.zipCode,
response.data.days?.[0]?.date response.data.days?.[0]?.date
), );
}); }
return globalMethods.callHttpClient(options);
}, },
getMobilePremiumFee(context, { pageNameToLog }) { getMobilePremiumFee(context, { pageNameToLog }) {
@ -3457,100 +3452,6 @@ export default createStore({
actions, actions,
}); });
// Private Functions
function getHasRecalibrationPart(state) {
return containsRecalParts(state.order.lineItems);
}
function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) {
if (!arrayOfObjects) return null;
return arrayOfObjects.sort((a, b) => {
if (a[propertyName] < b[propertyName]) return -1;
else if (a[propertyName] > b[propertyName]) return 1;
else return 0;
});
}
function convertGlassPieceNamingForApi(glassArray) {
if (!glassArray || glassArray.length === 0) return [];
// check if array already converted. (likely when a session has been saved previously and then reloaded)
if (glassArray[0].location !== undefined) {
return glassArray;
}
const converted = [];
glassArray.forEach((glass) => {
converted.push({
location: glass.glassLocation,
name: glass.glassName,
});
});
return converted;
}
function convertResultsForApi(resultsArray) {
if (!resultsArray) return [];
const converted = [];
resultsArray.forEach((answer) => {
converted.push({
location: answer.glassLocation,
name: answer.glassName,
result: answer.result,
});
});
return converted;
}
function convertGlassPieceNamingFromApi(glassArray) {
glassArray.forEach((glass) => {
glass.glassLocation = glass.glassPiece.location;
glass.glassName = glass.glassPiece.name;
delete glass.glassPiece;
return glass;
});
return glassArray;
}
function addPricesToLineItems(lineItems, pricingLineItems) {
lineItems.forEach((lineItem) => {
const lineItemIndex = pricingLineItems.findIndex(
(pricingLineItem) => pricingLineItem.partNumber === lineItem.partNumber
);
if (lineItem.childParts) {
addPricesToLineItems(lineItem.childParts, pricingLineItems);
}
const pricedLineItem = pricingLineItems.splice(lineItemIndex, 1)[0];
lineItem.laborAmount = pricedLineItem.laborAmount;
lineItem.sellingPrice = pricedLineItem.sellingPrice;
lineItem.kitPrice = pricedLineItem.kitPrice;
lineItem.salesTax = pricedLineItem.salesTax;
});
return lineItems;
}
function addTaxesToPricedLineItems(pricedLineItems, taxingLineItems = []) {
pricedLineItems.forEach((pricedLineItem) => {
const lineItemIndex = taxingLineItems.findIndex(
(taxingLineItem) => taxingLineItem.partNumber === pricedLineItem.partNumber
);
if (pricedLineItem.childParts) {
addTaxesToPricedLineItems(pricedLineItem.childParts, taxingLineItems);
}
const taxedLineItem = taxingLineItems.splice(lineItemIndex, 1)[0];
pricedLineItem.salesTax = taxedLineItem?.salesTax ?? 0;
});
return pricedLineItems;
}
export function mapTaxedLineItemsToStoreFormat(availableLineItems, storeLineItems) { export function mapTaxedLineItemsToStoreFormat(availableLineItems, storeLineItems) {
// clone the lineItems array because what we're passing in is referencing the store directly // clone the lineItems array because what we're passing in is referencing the store directly
const lineItems = deepClone(storeLineItems); const lineItems = deepClone(storeLineItems);
@ -3658,6 +3559,127 @@ export function getArrayOfAllLineItemsAndChildParts(lineItems) {
return consolidatedLineItemsArray; return consolidatedLineItemsArray;
} }
// Private Functions
function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) {
return (array ?? []).map((x) => x[propertyName]).filter((x) => x);
}
function getTimeSlotsAdditionalEventData(
provisionalTriggers,
zipCode,
firstAvailableAppointmentDateString,
shopAppointmentType
) {
var numberOfDays = null;
if (firstAvailableAppointmentDateString)
numberOfDays = getDateDifferenceInDays(
new Date().toISOString().split("T")[0],
firstAvailableAppointmentDateString
);
if (shopAppointmentType)
return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ShopAppointmentType:${shopAppointmentType},ProvisionalTriggers:${provisionalTriggers.join(
","
)}`;
else
return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ProvisionalTriggers:${provisionalTriggers.join(
","
)}`;
}
function getHasRecalibrationPart(state) {
return containsRecalParts(state.order.lineItems);
}
function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) {
if (!arrayOfObjects) return null;
return arrayOfObjects.sort((a, b) => {
if (a[propertyName] < b[propertyName]) return -1;
else if (a[propertyName] > b[propertyName]) return 1;
else return 0;
});
}
function convertGlassPieceNamingForApi(glassArray) {
if (!glassArray || glassArray.length === 0) return [];
// check if array already converted. (likely when a session has been saved previously and then reloaded)
if (glassArray[0].location !== undefined) {
return glassArray;
}
const converted = [];
glassArray.forEach((glass) => {
converted.push({
location: glass.glassLocation,
name: glass.glassName,
});
});
return converted;
}
function convertResultsForApi(resultsArray) {
if (!resultsArray) return [];
const converted = [];
resultsArray.forEach((answer) => {
converted.push({
location: answer.glassLocation,
name: answer.glassName,
result: answer.result,
});
});
return converted;
}
function convertGlassPieceNamingFromApi(glassArray) {
glassArray.forEach((glass) => {
glass.glassLocation = glass.glassPiece.location;
glass.glassName = glass.glassPiece.name;
delete glass.glassPiece;
return glass;
});
return glassArray;
}
function addPricesToLineItems(lineItems, pricingLineItems) {
lineItems.forEach((lineItem) => {
const lineItemIndex = pricingLineItems.findIndex(
(pricingLineItem) => pricingLineItem.partNumber === lineItem.partNumber
);
if (lineItem.childParts) {
addPricesToLineItems(lineItem.childParts, pricingLineItems);
}
const pricedLineItem = pricingLineItems.splice(lineItemIndex, 1)[0];
lineItem.laborAmount = pricedLineItem.laborAmount;
lineItem.sellingPrice = pricedLineItem.sellingPrice;
lineItem.kitPrice = pricedLineItem.kitPrice;
lineItem.salesTax = pricedLineItem.salesTax;
});
return lineItems;
}
function addTaxesToPricedLineItems(pricedLineItems, taxingLineItems = []) {
pricedLineItems.forEach((pricedLineItem) => {
const lineItemIndex = taxingLineItems.findIndex(
(taxingLineItem) => taxingLineItem.partNumber === pricedLineItem.partNumber
);
if (pricedLineItem.childParts) {
addTaxesToPricedLineItems(pricedLineItem.childParts, taxingLineItems);
}
const taxedLineItem = taxingLineItems.splice(lineItemIndex, 1)[0];
pricedLineItem.salesTax = taxedLineItem?.salesTax ?? 0;
});
return pricedLineItems;
}
function getFlattenedArrayOfLineItemsWithChildParts(lineItems, childPartRecursiveCall = false) { function getFlattenedArrayOfLineItemsWithChildParts(lineItems, childPartRecursiveCall = false) {
let flattenedArray = []; let flattenedArray = [];
lineItems?.forEach((lineItem) => { lineItems?.forEach((lineItem) => {
@ -3966,3 +3988,8 @@ function getExternalParameterDefaultState() {
function saveExternalParameterState(externalParameterState) { function saveExternalParameterState(externalParameterState) {
window.sessionStorage.setItem("externalParameterState", JSON.stringify(externalParameterState)); window.sessionStorage.setItem("externalParameterState", JSON.stringify(externalParameterState));
} }
const timeSlotCallFlags = {
shop: false,
mobile: false,
};

View file

@ -121,8 +121,8 @@ $body-color: $gray-600;
//Fonts //Fonts
$font-family-sans-serif: AvertaRegular, Arial, Helvetica, sans-serif; $font-family-sans-serif: AvertaRegular, Arial, Helvetica, sans-serif;
$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", $font-family-monospace:
monospace; SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", 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;
@ -170,8 +170,7 @@ $spacers: (
/* 16px */ 5: $spacer * 1.5, /* 16px */ 5: $spacer * 1.5,
/* 24px */ 6: $spacer * 2, /* 24px */ 6: $spacer * 2,
/* 32px */ 7: $spacer * 2.5, /* 32px */ 7: $spacer * 2.5,
/* 40px */ 8: $spacer * 3, /* 40px */ 8: $spacer * 3 /* 48px */,
/* 48px */
); );
//Enable negative spacing (does NOT work on padding) //Enable negative spacing (does NOT work on padding)