Merge branch 'develop' into feature/humphries/INSR-7759

This commit is contained in:
Alex Humphries 2026-02-20 10:20:39 -05:00
commit 10c371aec7
44 changed files with 831 additions and 856 deletions

View file

@ -47,7 +47,7 @@
:data-bs-toggle="includeSelectIcon ? 'modal' : ''"
:data-bs-target="'#' + cmsWidgetName"
@change="handleChange"
@blur="handleChange"
@blur="blurPlus"
@focus="$emit('focus', $event.target.value)"
@paste="trimOnPaste"
@drop="trimOnPaste" />
@ -92,6 +92,10 @@ export default {
buttonMain
},
props: {
alwaysEmitFieldErrorOnEvent: {
type: Boolean,
default: false
},
type: {
type: String,
default: 'text'
@ -136,7 +140,7 @@ export default {
default: () => {}
}
},
emits: ['focus', 'update:modelValue', 'textboxQuestionEvent.inputIdAssigned', 'click-event'],
emits: ['focus', 'update:modelValue', 'textboxQuestionEvent.inputIdAssigned', 'click-event', 'field-has-error'],
setup(props) {
const propsClone = { ...props };
const { modelValue } = propsClone;
@ -218,6 +222,12 @@ export default {
this.$emit('textboxQuestionEvent.inputIdAssigned', this.inputId);
},
methods: {
blurPlus() {
this.handleChange(this.value);
if (this.alwaysEmitFieldErrorOnEvent) {
this.$emit('field-has-error', !this.meta.valid);
}
},
trimOnPaste(evt) {
evt.stopPropagation();
evt.preventDefault();
@ -254,6 +264,8 @@ export default {
if (result.valid) {
this.handleChange(this.value);
this.$emit('click-event');
} else if (this.alwaysEmitFieldErrorOnEvent) {
this.$emit('field-has-error', true);
}
}
}
@ -291,6 +303,18 @@ input::-webkit-date-and-time-value {
}
.textbox-question {
&.has-error {
.input-wrapper.has-search-icon {
input.form-control[type='text'] {
border-color: #db0020;
}
button[type='submit'] {
border-color: #db0020;
background-color: #fbe5e9;
background-image: url($svg-error-search-icon);
}
}
}
label {
color: $black;
font-weight: 600;

View file

@ -94,10 +94,11 @@ export default {
}
if (error.response.status !== 404) {
global.$logger.logError(`${method}: ${endpoint}: ${error.message}`, error.response);
const errorData = { method, url, payload, error };
global.$logger.logError(`${method}: ${endpoint}`, errorData);
if (bailoutOnError && global.bailoutOnAxiosError !== undefined)
{
global.bailoutOnAxiosError({ url, error });
global.bailoutOnAxiosError(errorData);
}
}
return reject(error.response);

View file

@ -21,7 +21,8 @@ global.$logger = {
function setupMocksForHttpClient({
endpoint = null,
isError = false,
additionalData = null
additionalData = null,
bailoutOnError = false
}) {
// Clear node module
axios.mockClear();
@ -57,7 +58,8 @@ function setupMocksForHttpClient({
return {
endpoint,
logApiCall: true
logApiCall: true,
bailoutOnError
};
}
@ -92,3 +94,21 @@ it('Global Methods - Call Http Client - Should Reject Promise', () => {
expect(err.status).toEqual(500);
});
});
it('Global Methods - Call Http Client - Rejected Promised - bailoutOnError: true - Calls global.bailoutOnAxiosError', () => {
// Arrange
const endpoint = 'https://mock.safelite.com';
const httpArgs = setupMocksForHttpClient({
endpoint,
isError: true,
bailoutOnError: true
});
analyticsMixIn.methods.pushEventToGA = jest.fn();
global.bailoutOnAxiosError = jest.fn();
// Act
globalMethods.callHttpClient(httpArgs).catch((err) => {
// Assert
expect(global.bailoutOnAxiosError).toHaveBeenCalledTimes(1);
});
});

View file

@ -4,8 +4,8 @@ import submitType from '@/constants/submit-type';
/*
Encapsulates asynchronous Save Session logic inside a promise to allow for Save Session queuing
*/
async function saveSessionHelper(store, { submitAfterSave, createWorkOrderNumberForPIA }) {
const savedSessionInfo = await store.saveSession({ submitAfterSave, createWorkOrderNumberForPIA });
async function saveSessionHelper(store, { submitAfterSave, createWorkOrderNumberForPIA, bailoutOnError }) {
const savedSessionInfo = await store.saveSession({ submitAfterSave, createWorkOrderNumberForPIA, bailoutOnError });
if (savedSessionInfo) {
store.setSaveSessionInfo(savedSessionInfo.data);
}
@ -16,11 +16,11 @@ async function saveSessionHelper(store, { submitAfterSave, createWorkOrderNumber
This will also set Referral information in the store after saving, and then
update the cookie. To force synchronous behavior pass in 'true' for shouldAwaitSaveSessionQueue
*/
export async function saveSession({ shouldAwaitSaveSessionQueue = false, submitAfterSave = false, createWorkOrderNumberForPIA = false }) {
export async function saveSession({ shouldAwaitSaveSessionQueue = false, submitAfterSave = false, createWorkOrderNumberForPIA = false, bailoutOnError = false }) {
const store = useMainStore();
const saveSessionPromise = store.applicationUser.saveSessionPromise
? store.applicationUser.saveSessionPromise.then(() => saveSessionHelper(store, { submitAfterSave, createWorkOrderNumberForPIA }))
: saveSessionHelper(store, { submitAfterSave, createWorkOrderNumberForPIA });
? store.applicationUser.saveSessionPromise.then(() => saveSessionHelper(store, { submitAfterSave, createWorkOrderNumberForPIA, bailoutOnError }))
: saveSessionHelper(store, { submitAfterSave, createWorkOrderNumberForPIA, bailoutOnError });
store.setSaveSessionPromise(saveSessionPromise);
@ -38,7 +38,8 @@ export async function submitWorkOrder({ submitType }) {
store.resetSubmittedOrder();
await saveSession({
shouldAwaitSaveSessionQueue: true,
submitAfterSave: true
submitAfterSave: true,
bailoutOnError: true
});
await store.createSubmittedOrder(submitType);
}

View file

@ -157,8 +157,7 @@ export default {
this.navigateForward(this.partsOrQuestionsData, null);
},
requestCallbackBailout() {
this.mainStore.setBailout(bailoutMessage.RequestCallback());
this.$router.navigate(navigationScenarios.CLICKED_NEED_HELP_WITH_BAILOUT, this.$route);
this.$router.navigateBailout(bailoutMessage.RequestCallback());
}
}
};

View file

@ -5,6 +5,7 @@ Object {
"CANCEL_CLAIM_REF_NAME": "CancelClaimModal",
"RECAL_MODAL_REF_NAME": "RecalModal",
"baseServiceLineItems": Array [],
"isPageLoading": true,
"widget": Object {
"disclaimerText": "DisclaimerWidget",
"explanatoryText": "ExplanatoryTextWidget",

View file

@ -0,0 +1,116 @@
<template>
<div class="afterpay-modal-banner" role="alert">
<component
src="https://js.squarecdn.com/square-marketplace.js"
async
:is="'script'">
</component>
<div class="afterpay-content">
<span class="afterpay-copy" v-html="afterpayCopy"></span>
<span class="afterpay-copy">{{ afterpayPaymentAmount }}</span>
<span class="afterpay-copy" v-html="afterpayCopyTwo"></span>
<img :src="imageUrl" class="image" alt="Afterpay logo"/>
<span class="afterpay-copy">. &nbsp;</span>
<a
id="afterpay-learnmore"
href="#"
data-afterpay-modal="en_US"
data-bind="click:afterpayLearnMore"
class="afterpay-learn-more"
@click.prevent>
<span>Learn more</span>
</a>
</div>
</div>
</template>
<script>
import { setupModalLinks } from "@/helpers/cms-content-helper";
import { formatAmountInDollars } from '@/helpers/text-helper.js';
import widgetFields from '@/constants/cms-widget-fields.js';
import { useMainStore } from '@/store/index.js';
export default {
name: "afterpay-banner",
components: {
},
props: {
cmsWidgetName: String,
totalServicePrice: Number
},
setup() {
const mainStore = useMainStore();
return { mainStore };
},
data() {
return {};
},
computed: {
imageUrl() {
return this.getCmsContent(this.cmsWidgetName, widgetFields.CONTENT_GROUP_WIDGET.IMAGE);
},
afterpayCopy() {
return this.getCmsContent(this.cmsWidgetName, widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT);
},
afterpayCopyTwo() {
return this.getCmsContent(this.cmsWidgetName, widgetFields.CONTENT_GROUP_WIDGET.SUBHEADER_TEXT);
},
deductibleValue() {
return this.mainStore.currentDeductible;
},
afterpayPaymentAmount() {
return this.calculateAfterpayPaymentAmount;
},
isDeductibleScenario() {
return this.mainStore.isVerified && this.mainStore.isDeductible;
},
calculateAfterpayPaymentAmount() {
const totalAmount = this.isDeductibleScenario ? this.deductibleValue : this.totalServicePrice;
const afterpayAmount = (totalAmount / 4).toFixed(2);
return formatAmountInDollars(afterpayAmount);
}
}
};
</script>
<style lang="scss" scoped>
.afterpay-modal-banner {
background-color: $blue-100;
justify-content: center;
align-items: center;
padding: 0.5rem 1rem 0.5rem 1rem;
border-radius: 0.25rem;
margin-top: 20px;
}
.afterpay-content {
display: block;
text-align: center;
}
.afterpay-copy {
font-size: 0.875rem;
color: $blue-700;
font-weight: $font-weight-bold;
line-height: 1.5rem;
}
.image-and-link {
justify-content: center;
align-items: center;
}
.image {
border: 0;
height: 20px;
width: 83px;
}
:deep(a.afterpay-learn-more) {
color: $blue-700;
fill: $blue-700;
text-decoration: underline;
font-size: 0.875rem;
}
</style>

View file

@ -63,7 +63,8 @@ const loadingModalStub = {
function getMountedComponent(mainInitialState = {}, initialData = {}, methodToRun = () => {}) {
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
navigate: jest.fn(),
navigateBailout: jest.fn()
}
});
@ -134,7 +135,10 @@ describe('coverageStatement.vue', () => {
});
test('Should render site subheader', () => {
// Arrange
const { wrapper } = getMountedComponent({});
const initialDataForTest = {
isPageLoading: false
};
const { wrapper } = getMountedComponent({}, initialDataForTest);
// Act
const subheader = wrapper.find({ ref: 'siteSubHeader' });
@ -145,7 +149,10 @@ describe('coverageStatement.vue', () => {
test('Should render explanatory text', () => {
// Arrange
const { wrapper } = getMountedComponent({});
const initialDataForTest = {
isPageLoading: false
};
const { wrapper } = getMountedComponent({}, initialDataForTest);
// Act
const explanatoryText = wrapper.find({ ref: 'explanatoryText' });
@ -155,7 +162,10 @@ describe('coverageStatement.vue', () => {
});
test('Should render secondary text', () => {
// Arrange
const { wrapper } = getMountedComponent({});
const initialDataForTest = {
isPageLoading: false
};
const { wrapper } = getMountedComponent({}, initialDataForTest);
// Act
const secondaryText = wrapper.find({ ref: 'secondaryText' });
@ -430,6 +440,109 @@ describe('coverageStatement.vue', () => {
expect(result).toBe(expected);
});
});
describe('displayAfterpayBanner', () => {
test('returns true if ITAC', () => {
// Arrange
const mainInitialState = {
order: {
insuranceCoverage: {
coverageStatus: coverageStatuses.VERIFIED,
coverageType: coverageType.ITAC
}
}
};
const { wrapper } = getMountedComponent(mainInitialState);
// Act
const result = wrapper.vm.displayAfterpayBanner;
// Assert
expect(result).toBeTruthy();
});
test('returns true if No Comp', () => {
// Arrange
const mainInitialState = {
order: {
insuranceCoverage: {
coverageStatus: coverageStatuses.VERIFIED,
coverageType: coverageType.NO_COMP
}
}
};
const { wrapper } = getMountedComponent(mainInitialState);
// Act
const result = wrapper.vm.displayAfterpayBanner;
// Assert
expect(result).toBeTruthy();
});
test('returns true if deductible is > $0', () => {
// Arrange
const mainInitialState = {
order: {
insuranceCoverage: {
coverageStatus: coverageStatuses.VERIFIED,
coverageType: coverageType.Deductible
},
damage: {
isRepair: false
},
currentDeductible: {
replace: 500
}
}
};
const { wrapper } = getMountedComponent(mainInitialState);
// Act
const result = wrapper.vm.displayAfterpayBanner;
// Assert
expect(result).toBeTruthy();
});
test('returns false if deductible is <= $0', () => {
// Arrange
const mainInitialState = {
order: {
insuranceCoverage: {
coverageStatus: coverageStatuses.VERIFIED,
coverageType: coverageType.Deductible
},
damage: {
isRepair: false
},
currentDeductible: {
replace: 0
}
}
};
const { wrapper } = getMountedComponent(mainInitialState);
// Act
const result = wrapper.vm.displayAfterpayBanner;
// Assert
expect(result).toBeFalsy();
});
test('returns false if Unverified', () => {
// Arrange
const mainInitialState = {
order: {
insuranceCoverage: {
coverageStatus: coverageStatuses.UNVERIFIED,
}
}
};
const { wrapper } = getMountedComponent(mainInitialState);
// Act
const result = wrapper.vm.displayAfterpayBanner;
// Assert
expect(result).toBeFalsy();
});
});
});
describe('methods', () => {
describe('arePagePrerequisitesValid', () => {
@ -578,7 +691,7 @@ describe('coverageStatement.vue', () => {
undefined
);
});
test('If Verified ITAC, selected Cancel, navigate forward w/ CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP', () => {
test('If Verified ITAC, selected Cancel, navigateBailout', () => {
// Arrange
const deductible = servicePrice + 1;
const mainInitialState = {
@ -604,11 +717,7 @@ describe('coverageStatement.vue', () => {
wrapper.vm.cancelClaim();
// Assert
expect(wrapper.vm.$router.navigate)
.toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP,
undefined
);
expect(wrapper.vm.$router.navigateBailout).toHaveBeenCalled();
});
test('If No Comp and selected Safelite, navigate forward with CLICKED_FORWARD_WITH_SAFELITE scenario', () => {
// Arrange
@ -641,7 +750,7 @@ describe('coverageStatement.vue', () => {
undefined
);
});
test('If No comp and selected other shop, navigate forward with CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP ', () => {
test('If No comp and selected other shop, navigateBailout ', () => {
// Arrange
const mainInitialState = {
order: {
@ -666,11 +775,7 @@ describe('coverageStatement.vue', () => {
wrapper.vm.cancelClaim();
// Assert
expect(wrapper.vm.$router.navigate)
.toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP,
undefined
);
expect(wrapper.vm.$router.navigateBailout).toHaveBeenCalled();
});
});
describe('openCancelClaimModal', () => {

View file

@ -10,7 +10,8 @@
cmsWidgetName="SiteHeaderWidget" />
</div>
<div class="iss-heritage-container-width">
<div class="coverage-statement-container iss-heritage-content-container-width">
<div v-if="!isPageLoading"
class="coverage-statement-container iss-heritage-content-container-width">
<h5
ref="siteSubHeader"
class="sub-header text-black"
@ -69,6 +70,10 @@
class="d-flex justify-content-center cost mb-0 cost-underline">
{{ formatAmountInDollars(totalServicePrice) }}
</div>
<afterpayModalBanner
v-if="displayAfterpayBanner"
cmsWidgetName="AfterpayModalWidget"
:totalServicePrice="totalServicePrice" />
<buttonMain
ref="buttonMain"
class="full-width-button mt-5 mb-5"
@ -117,6 +122,7 @@ import textBlock from '@/digital-components/text-block/text-block.vue';
import textLink from '@/ux-components/text-link/text-link.vue';
import buttonMain from '@/ux-components/button-main/button-main.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import afterpayModalBanner from '@/layouts/coverage-statement/afterpay-modal-banner/afterpay-modal-banner.vue';
// Import Supporting Files
import {
fetchCmsContentForPage,
@ -151,7 +157,8 @@ export default {
textBlock,
textLink,
siteFooter,
cancelClaimModal
cancelClaimModal,
afterpayModalBanner
},
mixins: [baseFormMixin, vehicleQuestionsMixin],
async beforeRouteEnter(to, from, next) {
@ -206,6 +213,7 @@ export default {
data() {
return {
baseServiceLineItems: [],
isPageLoading: true,
widget: {
disclaimerText: 'DisclaimerWidget',
subheader: 'SiteSubHeaderWidget',
@ -337,6 +345,11 @@ export default {
return 'centered-back-button';
}
return '';
},
displayAfterpayBanner() {
return this.isITACQuoteVisible
|| this.isNoCompQuoteVisible
|| (this.showDeductibleOnly && this.deductibleValue > 0);
}
},
watch: {
@ -347,6 +360,9 @@ export default {
}
},
mounted() {
if (this.isPageLoading) {
showIssLoadingModal(true);
}
setupModalLinks(this);
},
methods: {
@ -370,6 +386,7 @@ export default {
}
showIssLoadingModal(false);
this.isPageLoading = false;
},
async getPricedParts() {
const { glassParts, supportingItems } = this.mainStore.order.lineItems;
@ -380,18 +397,7 @@ export default {
// We only call the ITAC pricing endpoint if we are not repair or we are NoComp
if (!this.isRepair || this.mainStore.isNoComp) {
const pricingResults = await useMainStore().getITACPriceOrderItems(availableLineItems)
.catch((err) => {
useMainStore().setBailout(bailoutMessage.pricingResponseError(
availableLineItems.map((li) => li.partNumber),
{
code: err.code,
message: err.message,
data: err.data
}
));
this.navigateWithScenario(navigationScenarios.PRICING_LOOKUP_ERROR);
});
const pricingResults = await useMainStore().getITACPriceOrderItems(availableLineItems);
this.setBaseServiceLineItems(pricingResults);
}
},
@ -402,8 +408,7 @@ export default {
this.mainStore.updateIsSafeliteProvider(true);
this.navigateWithScenario(navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE);
} else {
this.mainStore.setBailout(bailoutMessage.coverageStatementInvalidState());
this.navigateWithScenario(navigationScenarios.CLICKED_FORWARD_WITH_INVALID_STATE);
this.$router.navigateBailout(bailoutMessage.coverageStatementInvalidState());
}
},
navigateWithScenario(scenario) {
@ -457,8 +462,7 @@ export default {
},
cancelClaim() {
this.mainStore.updateIsSafeliteProvider(false);
this.mainStore.setBailout(bailoutMessage.RequestCallback());
this.navigateWithScenario(navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP);
this.$router.navigateBailout(bailoutMessage.RequestCallback());
}
}
};

View file

@ -357,11 +357,15 @@ describe('duplicateCheck.vue', () => {
useMainStore().loadSession = jest.fn().mockImplementation(() => Promise.reject(error));
// Act
await wrapper.vm.forwardButtonAction();
// Assert
expect.assertions(2);
try {
await wrapper.vm.forwardButtonAction();
} catch (e) {
expect(e).toMatch(error);
}
expect(wrapper.vm.mainStore.loadSession).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigate).toHaveBeenCalledTimes(1);
});
test('coverageType deductible and policy vehicles returned => CLICKED_FORWARD_POLICY_VERIFIED_WITH_VEHICLES', async () => {
// Arrange

View file

@ -140,18 +140,14 @@ export default {
return;
}
try {
const selectedReferral =
this.mainStore.applicationUser.duplicateOrders.find((o) => o.correlationId === this.selectedAnswer);
const selectedReferral =
this.mainStore.applicationUser.duplicateOrders.find((o) => o.correlationId === this.selectedAnswer);
if (selectedReferral) {
await this.mainStore.loadSession(selectedReferral);
}
} catch (err) {
console.error(`Error on loading session from duplicate check ${err}`);
} finally {
this.navigateForward();
if (selectedReferral) {
await this.mainStore.loadSession(selectedReferral);
}
this.navigateForward();
},
navigateForward() {
this.mainStore.updateDuplicateCheckVisited(true);

View file

@ -34,44 +34,51 @@ export default {
computed: {
},
async mounted() {
const queryStringParams = this.parseQueryParms();
const { isAuthorized, clientData, decryptedParams } = await this.validateClientTagOnEntry(queryStringParams);
this.unauthorized = !isAuthorized;
if (!isAuthorized) {
// Remove the loading animation if client tag validation fails so users can see the Unauthorized Access message.
showIssLoadingModal(false);
return;
}
this.populateISSConfigValues(clientData);
try {
// Check cookie
const issCookie = getISSCookie();
if (issCookie !== null && issCookie.VehicleMake && issCookie.VehicleModel) {
const clientParentAccountNumber = clientData.parentAccountNumber;
const cookieParentAccountNumber = issCookie.ReferralParentAccountNumber;
const queryStringParams = this.parseQueryParms();
if (clientParentAccountNumber === cookieParentAccountNumber) {
const savedSessionTimeStamp = new Date(issCookie.SavedSessionTimeoutDate);
const isSavedSessionTimedOut = new Date(new Date().toUTCString()) > savedSessionTimeStamp;
const { isAuthorized, clientData, decryptedParams } = await this.validateClientTagOnEntry(queryStringParams);
if (!isSavedSessionTimedOut) {
this.mainStore.issConfig.enableContinueFromCookie = true;
this.unauthorized = !isAuthorized;
if (!isAuthorized) {
// Remove the loading animation if client tag validation fails so users can see the Unauthorized Access message.
showIssLoadingModal(false);
return;
}
this.populateISSConfigValues(clientData);
try {
// Check cookie
const issCookie = getISSCookie();
if (issCookie !== null && issCookie.VehicleMake && issCookie.VehicleModel) {
const clientParentAccountNumber = clientData.parentAccountNumber;
const cookieParentAccountNumber = issCookie.ReferralParentAccountNumber;
if (clientParentAccountNumber === cookieParentAccountNumber) {
const savedSessionTimeStamp = new Date(issCookie.SavedSessionTimeoutDate);
const isSavedSessionTimedOut = new Date(new Date().toUTCString()) > savedSessionTimeStamp;
if (!isSavedSessionTimedOut) {
this.mainStore.issConfig.enableContinueFromCookie = true;
}
}
} else {
updateOrCreateISSCookie(true);
}
} else {
} catch {
updateOrCreateISSCookie(true);
}
} catch {
updateOrCreateISSCookie(true);
}
if (clientData.parameters?.length > 0) {
const finalParams = this.combineClientParameters(clientData.parameters, { ...queryStringParams, ...decryptedParams });
this.populateStoreItemsFromParams(finalParams);
if (clientData.parameters?.length > 0) {
const finalParams = this.combineClientParameters(clientData.parameters, { ...queryStringParams, ...decryptedParams });
this.populateStoreItemsFromParams(finalParams);
}
} catch (e) {
global.$logger.logError('[Entry Page] Client Setup Error:', e);
this.unauthorized = true;
showIssLoadingModal(false);
return;
}
this.mainStore.applicationUser.coverageAttempts = 0;
@ -151,37 +158,33 @@ export default {
this.mainStore.issConfig.isCoverageEnabled = data.coverageEnabled;
this.mainStore.issConfig.siteType = data.siteType;
try {
if (data.clientFlags) {
const clientFlags = JSON.parse(data.clientFlags);
if (data.clientFlags) {
const clientFlags = JSON.parse(data.clientFlags);
if (clientFlags.TPAEnabled) {
this.mainStore.issConfig.enableTPAFlow = true;
}
if (clientFlags.ClientFullName != null) {
this.mainStore.issConfig.clientFullName = clientFlags.ClientFullName;
}
if (clientFlags.ClientDisplayName != null) {
this.mainStore.issConfig.clientDisplayName = clientFlags.ClientDisplayName;
this.mainStore.issConfig.clientPossessiveName = toPossessive(clientFlags.ClientDisplayName);
}
if (clientFlags.ClientPossessiveName != null) {
this.mainStore.issConfig.clientPossessiveName = clientFlags.ClientPossessiveName;
}
if (clientFlags.ClaimRegistrationRequired) {
this.mainStore.issConfig.isClaimRegistrationRequired = true;
}
if (clientFlags.EnableNoCompQuote) {
this.mainStore.issConfig.enableNoCompQuote = true;
}
if (clientFlags.TPAEnabled) {
this.mainStore.issConfig.enableTPAFlow = true;
}
if (clientFlags.ClientFullName != null) {
this.mainStore.issConfig.clientFullName = clientFlags.ClientFullName;
}
if (clientFlags.ClientDisplayName != null) {
this.mainStore.issConfig.clientDisplayName = clientFlags.ClientDisplayName;
this.mainStore.issConfig.clientPossessiveName = toPossessive(clientFlags.ClientDisplayName);
}
if (clientFlags.ClientPossessiveName != null) {
this.mainStore.issConfig.clientPossessiveName = clientFlags.ClientPossessiveName;
}
if (clientFlags.ClaimRegistrationRequired) {
this.mainStore.issConfig.isClaimRegistrationRequired = true;
}
if (clientFlags.EnableNoCompQuote) {
this.mainStore.issConfig.enableNoCompQuote = true;
}
} catch (e) {
console.error(`Error parsing client flags: ${e}`);
}
},
combineClientParameters(configParams, queryStringParams) {

View file

@ -148,8 +148,7 @@ export default {
this.navigateForward(partsOrQuestions, null);
},
requestCallbackBailout() {
this.mainStore.setBailout(bailoutMessage.RequestCallback());
this.$router.navigate(navigationScenarios.CLICKED_NEED_HELP_WITH_BAILOUT, this.$route);
this.$router.navigateBailout(bailoutMessage.RequestCallback());
}
}
};

View file

@ -148,8 +148,7 @@ export default {
this.navigateForward(glassPartsForStore, null);
},
requestCallbackBailout() {
this.mainStore.setBailout(bailoutMessage.RequestCallback());
this.$router.navigate(navigationScenarios.CLICKED_NEED_HELP_WITH_BAILOUT, this.$route);
this.$router.navigateBailout(bailoutMessage.RequestCallback());
}
},
};

View file

@ -410,25 +410,16 @@ export default {
}
}
if (this.paymentMethod === paymentMethods.PAY_AT_TIME_OF_SERVICE) {
try {
await submitWorkOrder({ submitType: submitType.SAFELITE });
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
} catch (error) {
useMainStore().setBailout(bailoutMessage.saveSessionError(error.data));
this.$router.navigate(
this.navigationScenarios.SAVE_SESSION_FAILED,
this.$route,
{ issPage: issPageValues.PAYMENT_METHOD }
);
console.error(`error: response from submit work order:${error.message}`);
}
await submitWorkOrder({ submitType: submitType.SAFELITE });
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
} else {
await saveSession({
createWorkOrderNumberForPIA: true,
shouldAwaitSaveSessionQueue: true
shouldAwaitSaveSessionQueue: true,
bailoutOnError: true
});
this.$router.navigate(

View file

@ -124,13 +124,7 @@ export default {
},
async saveAndSubmitWorkOrder() {
// Final work order submit after returning from pay in advance.
try {
await submitWorkOrder({ submitType: submitType.SAFELITE });
} catch (error) {
console.error(`error: response from submit work order:${error.message}`);
this.navigateOnPayInAdvanceError();
return;
}
await submitWorkOrder({ submitType: submitType.SAFELITE });
this.$router.navigate(
this.navigationScenarios.PAY_IN_ADVANCE_SUCCESS,

View file

@ -252,12 +252,12 @@ describe('policy-vehicles.vue', () => {
);
test(
'Error in lookupVehicleByVin call => bailout true and navigate forward with CLICKED_FORWARD_WITH_BAILOUT scenario.',
'Error in lookupVehicleByVin call => error thrown in forwardButtonAction.',
async () => {
// Arrange
const { wrapper } = setupMocks({});
const lookupReturnValue = { error: true, status: 500, data: 'error' };
wrapper.vm.lookupVehicleByVin = jest.fn().mockReturnValue(lookupReturnValue);
wrapper.vm.lookupVehicleByVin = jest.fn().mockReturnValue(Promise.reject(lookupReturnValue));
const vin = getRandomString(17, 17);
await wrapper.setData({
@ -270,21 +270,14 @@ describe('policy-vehicles.vue', () => {
});
// Act
wrapper.vm.mainStore.applicationUser.pageData[issPageValues.BAILOUT_PAGE] = {
'bailout-page': {
bailoutCode: bailoutCode.VehicleVinLookupError
}
};
await wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.vm.mainStore.setBailout).toHaveBeenCalledWith(bailoutMessage.vehicleVinLookupError(vin, lookupReturnValue.data));
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
undefined,
{},
{}
);
expect.assertions(1);
try {
await wrapper.vm.forwardButtonAction();
} catch (e) {
expect(e).toBe(lookupReturnValue);
}
}
);
@ -292,7 +285,7 @@ describe('policy-vehicles.vue', () => {
async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.lookupVehicleByVin = jest.fn().mockReturnValue({ error: true, status: 404 });
wrapper.vm.lookupVehicleByVin = jest.fn().mockRejectedValue({ isAxiosError: true, status: 404 });
const vin = getRandomString(17, 17);
await wrapper.setData({

View file

@ -59,7 +59,6 @@ import vehicleSelectionOptions from '@/constants/vehicle-selection-options.js';
import endorsementOptions from '@/constants/endorsement-options.js';
import globalRules from '@/constants/global-rules.js';
import { useMainStore } from '@/store/index.js';
import bailoutMessage from '@/constants/bailoutMessage';
import {
deductibleForSelectedVehicle,
endorsementsForSelectedVehicle,
@ -144,10 +143,6 @@ export default {
},
repairWaivedForSelectedVehicle() {
return repairWaivedForSelectedVehicle(this.selectedPolicyVehicle);
},
selectedVehicle() {
const vehicle = this.mainStore.lookupVehicleByVin(this.selectedVehicleVin);
return vehicle;
}
},
watch: {
@ -156,15 +151,12 @@ export default {
if (value === vehicleSelectionOptions.VEHICLE_NOT_LISTED) {
// clear previously selected vehicle and image
this.mainStore.resetVehicleState();
} else {
// get vehicle details from selected VIN
const vehicle = await this.lookupVehicleByVin(value);
return;
}
// handle error in case vehicle info doesn't come back for selected VIN
if (vehicle?.error === true) {
this.mainStore.resetVehicleState();
return;
}
// get vehicle details from selected VIN
try {
const vehicle = await this.lookupVehicleByVin(value);
if (!vehicle?.data.canSafeliteService) {
this.displayNoServiceAlert = true;
return;
@ -177,6 +169,8 @@ export default {
vin: value
});
}
} catch (e) {
this.mainStore.resetVehicleState();
}
}
},
@ -196,33 +190,16 @@ export default {
);
},
async forwardButtonAction() {
if (this.selectedVehicleVin !== vehicleSelectionOptions.VEHICLE_NOT_LISTED) {
if (this.selectedVehicleVin === vehicleSelectionOptions.VEHICLE_NOT_LISTED) {
useMainStore().updateCoverageType(coverageType.NONE);
useMainStore().updateCoverageStatus(coverageStatuses.NO_COVERAGE);
this.navigateForward();
return;
}
const vehicle = this.policyVehicles.find((pv) => pv.vin === this.selectedVehicleVin);
try {
const vehicleLookupResponse = await this.lookupVehicleByVin(this.selectedVehicleVin);
const vehicle = this.policyVehicles.find((pv) => pv.vin === this.selectedVehicleVin);
if (vehicleLookupResponse.error) {
if (vehicleLookupResponse.status === 404) {
this.mainStore.resetVehicleState();
useMainStore().updateVehicle({
policyVehicleId: vehicle.id,
carId: '0',
category: '',
year: vehicle.vehicleYear || '',
make: vehicle.vehicleMake || '',
model: vehicle.vehicleModel || '',
style: vehicle.vehicleStyle || '',
vin: vehicle.vin
});
this.policyVinFound = false;
return this.navigateForward();
}
this.mainStore.setBailout(bailoutMessage.vehicleVinLookupError(
vehicle.vin,
vehicleLookupResponse.data
));
return this.navigateForward();
}
useMainStore().updateVehicle({
...vehicleLookupResponse.data,
@ -235,21 +212,30 @@ export default {
repairWaived: this.repairWaivedForSelectedVehicle,
endorsements: this.endorsementsForSelectedVehicle
});
} else {
useMainStore().updateCoverageType(coverageType.NONE);
useMainStore().updateCoverageStatus(coverageStatuses.NO_COVERAGE);
this.navigateForward();
} catch (e) {
if (e.isAxiosError && e.status === 404) {
this.mainStore.resetVehicleState();
useMainStore().updateVehicle({
policyVehicleId: vehicle.id,
carId: '0',
category: '',
year: vehicle.vehicleYear || '',
make: vehicle.vehicleMake || '',
model: vehicle.vehicleModel || '',
style: vehicle.vehicleStyle || '',
vin: vehicle.vin
});
this.policyVinFound = false;
this.navigateForward();
return;
}
throw e;
}
return this.navigateForward();
},
navigateForward() {
if (this.mainStore.isBailout) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
this.$route,
{},
{}
);
} else if (this.selectedVehicleVin === vehicleSelectionOptions.VEHICLE_NOT_LISTED) {
if (this.selectedVehicleVin === vehicleSelectionOptions.VEHICLE_NOT_LISTED) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_NON_LISTED_VEHICLE,
this.$route,
@ -281,15 +267,7 @@ export default {
}
},
async lookupVehicleByVin(vin) {
try {
return await useMainStore().lookupVehicleByVin(vin);
} catch (responseError) {
return {
error: true,
status: responseError.status,
data: responseError.data
};
}
return useMainStore().lookupVehicleByVin(vin);
},
async addAnotherVehicle() {
this.selectedVehicleVin = vehicleSelectionOptions.VEHICLE_NOT_LISTED;

View file

@ -20,7 +20,8 @@ jest.mock('@/helpers/cms-content-helper', () => ({
function setupMocks(mockApiResponses) {
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
navigate: jest.fn(),
navigateBailout: jest.fn()
},
route: 'provider-preference'
});
@ -43,7 +44,7 @@ function setupMocks(mockApiResponses) {
}
describe('provider-preference.vue', () => {
test('Should navigate to tpa disabled route page when TPAOption selected and TPA Flow disabled', () => {
test('Should navigateBailout when TPAOption selected and TPA Flow disabled', () => {
// Arrange
const { wrapper } = setupMocks();
@ -52,7 +53,7 @@ describe('provider-preference.vue', () => {
wrapper.vm.findAnotherShopClicked();
// Test
expect(wrapper.vm.$router.navigate).toBeCalledWith(navigationScenarios.CLICKED_FORWARD_WITH_TPA_DISABLED, 'provider-preference');
expect(wrapper.vm.$router.navigateBailout).toBeCalled();
});
test('Should navigate to safelite flow when navigateWithTPARecalAnswer is called with SafeliteOption', () => {

View file

@ -186,9 +186,7 @@ export default {
this.scheduleWithTPA();
}
} else {
this.mainStore.setBailout(bailoutMessage.TPANotEnabled());
const scenario = this.navigationScenarios.CLICKED_FORWARD_WITH_TPA_DISABLED;
this.navigateForward(scenario);
this.$router.navigateBailout(bailoutMessage.TPANotEnabled());
}
},
openStateSteeringModal() {

View file

@ -171,20 +171,20 @@ const getAvailableDates = async (
await Promise.all(storeActionConfigs.map(async (storeAction) => {
let timeSlotsResponse = null;
if (storeAction.storeAction === GET_SHOP_TIME_SLOTS) {
timeSlotsResponse = await useMainStore().getShopTimeSlots(
storeAction.payload.startDate,
storeAction.payload.endDate,
storeAction.payload.shopAppointmentType,
storeAction.payload.providerNumber
);
if (storeAction.payload.providerNumber) {
timeSlotsResponse = await useMainStore().getShopTimeSlots(
storeAction.payload.startDate,
storeAction.payload.endDate,
storeAction.payload.shopAppointmentType,
storeAction.payload.providerNumber
);
}
} else if (storeAction.payload?.zipCodeOverride) {
timeSlotsResponse = await useMainStore().getMobileTimeSlots(
storeAction.payload.startDate,
storeAction.payload.endDate,
storeAction.payload.zipCodeOverride
).catch(() => {
console.warn('Error fetching mobile time slots...');
});
);
}
if (!timeSlotsResponse || !timeSlotsResponse.data) {
@ -204,6 +204,9 @@ const getAvailableDates = async (
// sort days chronologically
timeSlotsResponsesData.days.sort(compareDayStrings);
return timeSlotsResponsesData;
})
.catch(() => {
return timeSlotsResponsesData;
});
};

View file

@ -2,6 +2,7 @@
<textboxQuestion
ref="zipInputTextQuestion"
v-model="internalZipcode"
:alwaysEmitFieldErrorOnEvent="true"
:cmsWidgetName="cmsWidgetName"
inputId="serviceZipCode"
cornerStyle="rounded"
@ -9,7 +10,8 @@
isRequired
:hasError="hasError"
:validationRules="`zip-required|${cmsWidgetName}-zip-format`"
@clickEvent="updateModelValue" />
@clickEvent="updateModelValue"
@fieldHasError="handleFieldError" />
</template>
<script>
@ -41,7 +43,7 @@ export default {
default: false
}
},
emits: ['update:modelValue'],
emits: ['update:modelValue', 'field-has-error'],
data() {
return {
internalZipcode: this.modelValue
@ -58,6 +60,9 @@ export default {
defineRule(`${this.cmsWidgetName}-zip-format`, regex(/^\d{5}$/, this.serviceZipFormatErrorMessage));
},
methods: {
handleFieldError(hasError) {
this.$emit('field-has-error', hasError);
},
updateModelValue() {
this.$emit('update:modelValue', this.internalZipcode);
}

View file

@ -77,7 +77,8 @@
:ref="SERVICE_ZIP_QUESTION_REF_NAME"
v-model="internalZipcode"
customInputId="serviceZipCode"
:cmsWidgetName="textboxQuestionWidgetName" />
:cmsWidgetName="textboxQuestionWidgetName"
@fieldHasError="handleFieldError" />
<div
v-if="errorMessage"
ref="errorMessageDiv"
@ -327,6 +328,11 @@ export default {
}
return addressLine2;
},
handleFieldError(hasChildFormFieldError) {
if (hasChildFormFieldError) {
this.errorMessage = '';
}
},
forceServiceZipRerender() {
this.renderServiceZip = false;
this.$nextTick(() => {

View file

@ -53,7 +53,8 @@ const loaderStub = {
function getMountedComponent(mainInitialState = {}, initialData = {}, methodToRun = () => {}) {
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
navigate: jest.fn(),
navigateBailout: jest.fn()
},
route: {
query: { issPage: 'tpa-search' }
@ -787,7 +788,7 @@ describe('tpa-search.vue', () => {
});
describe('needHelpLinkClick', () => {
test('sets bailout and navigates', () => {
test('navigatesBailout', () => {
// Arrange
const { wrapper } = getMountedComponent();
@ -795,11 +796,7 @@ describe('tpa-search.vue', () => {
wrapper.vm.needHelpLinkClick();
// Assert
expect(wrapper.vm.mainStore.setBailout).toHaveBeenCalled();
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_NEED_HELP,
expect.anything()
);
expect(wrapper.vm.$router.navigateBailout).toBeCalled();
});
});

View file

@ -165,7 +165,9 @@ export default {
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
const zipCode = useMainStore().order.customer.address.zipCode;
const serviceZipCode = useMainStore().order.serviceLocation.zipCode;
const customerZipCode = useMainStore().order.customer.address.zipCode;
const zipCode = serviceZipCode || customerZipCode;
const pageData = useMainStore().pageData(to.query.issPage);
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
const providersPromise = useMainStore().getTpaAndSafeliteProviders(zipCode, pageData?.tpaSearchValue ?? '');
@ -295,7 +297,7 @@ export default {
const provider = this.providers?.find((p) => p.providerNumber === newNumber);
if (provider && this.dataLoaded) {
useMainStore().updateServiceLocation({
zipCode: this.zipCode,
zipCode: this.mapZipCode,
provider: {
providerNumber: provider?.providerNumber,
address: {
@ -358,11 +360,7 @@ export default {
return getTpaProvidersResult?.data ?? [];
},
needHelpLinkClick() {
this.mainStore.setBailout(bailoutMessage.RequestCallback());
this.$router.navigate(
this.navigationScenarios.CLICKED_NEED_HELP,
this.$route
);
this.$router.navigateBailout(bailoutMessage.RequestCallback());
},
async searchClick() {
if (isNaN(this.tpaSearchValue)) {

View file

@ -315,20 +315,8 @@ export default {
};
},
async forwardButtonAction() {
try {
await submitWorkOrder({ submitType: submitType.TPA }).then(() => {
this.navigate(this.navigationScenarios.CLICKED_FORWARD);
}).catch((submitError) => {
this.mainStore.setBailout(bailoutMessage.saveSessionError(submitError.data));
this.navigate(
this.navigationScenarios.SAVE_SESSION_FAILED,
this.$route,
{ issPage: this.issPageValues.TPA_SUBMIT }
);
});
} catch (error) {
console.error(`error: response from submit work order:${error.message}`);
}
await submitWorkOrder({ submitType: submitType.TPA })
this.navigate(this.navigationScenarios.CLICKED_FORWARD);
},
navigate(scenario) {
this.$router.navigate(scenario, this.$route);

View file

@ -139,7 +139,7 @@ describe('vehicle-damage.vue', () => {
expect(vehicleQuestionsMixin.methods.getPartsOrQuestions).toHaveBeenCalledTimes(1);
expect(vehicleQuestionsMixin.methods.navigateForward).toHaveBeenCalledTimes(1);
});
test('Error in getPartsOrQuestions call => bailout true and navigate forward with CLICKED_FORWARD_WITH_BAILOUT scenario', async () => {
test('Error in getPartsOrQuestions call => error thrown in forwardButtonAction', async () => {
mountOptions.global.plugins = [createTestingPinia({
initialState: {
main: {
@ -154,23 +154,21 @@ describe('vehicle-damage.vue', () => {
}
}
})];
mountOptions.data = () => ({
hasBailedOut: true
});
mountOptions.data = () => ({});
const wrapper = mount(VehicleDamageComponent, mountOptions);
const siteFooterWrapper = wrapper.getComponent({ ref: 'siteFooter' });
const partsQuestionsErrorResponse = {
error: 'Error getting parts'
};
vehicleQuestionsMixin.methods.getPartsOrQuestions.mockImplementation(() => (
partsQuestionsErrorResponse
Promise.reject(partsQuestionsErrorResponse)
));
siteFooterWrapper.vm.$emit('forwardClicked');
await flushPromises();
expect(mockRouter.navigate).toHaveBeenCalledTimes(1);
expect(mockRouter.navigate)
.toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT, mockRoute);
expect.assertions(1);
try {
await wrapper.vm.forwardButtonAction();
} catch (e) {
expect(e).toBe(partsQuestionsErrorResponse);
}
});
});

View file

@ -189,8 +189,7 @@ export default {
this.getPassengerSideReplaceOptionsFromStore()
},
selectedWindshieldOptions: this.getWindshieldOptionsFromStore(),
selectedRearReplaceOptions: this.getRearReplaceOptionsFromStore(),
hasBailedOut: false
selectedRearReplaceOptions: this.getRearReplaceOptionsFromStore()
};
},
computed: {
@ -429,23 +428,12 @@ export default {
// If vin already exists or not replacing windshield, get parts/questions and navigate forward
const partsOrQuestionsResponse = await this.getPartsOrQuestions();
if (partsOrQuestionsResponse.error) {
this.mainStore.setBailout(bailoutMessage.PartsServiceError(partsOrQuestionsResponse.error.data));
window.console.error('Error on retrieving PartsOrQuestions');
this.hasBailedOut = true;
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
this.$route
);
}
// Comes from vehicleQuestionsMixin.navigateForward()
if (!this.hasBailedOut) {
await this.navigateForward(
partsOrQuestionsResponse.data.partsOrQuestions,
this
);
}
await this.navigateForward(
partsOrQuestionsResponse.data.partsOrQuestions,
this
);
} else {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITHOUT_VIN,

View file

@ -113,21 +113,10 @@ export default {
break;
case vinLookupMethodSelections.NOVIN:
const partsOrQuestionsResponse = await this.getPartsOrQuestions();
if (partsOrQuestionsResponse.error) {
this.mainStore.setBailout(bailoutMessage.PartsServiceError(partsOrQuestionsResponse.error.data));
window.console.error('Error on retrieving PartsOrQuestions');
this.hasBailedOut = true;
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
this.$route
);
}
else {
await this.navigateForward(
partsOrQuestionsResponse.data.partsOrQuestions,
this
);
}
await this.navigateForward(
partsOrQuestionsResponse.data.partsOrQuestions,
this
);
break;
default:
break;

View file

@ -64,6 +64,7 @@ import vehicleQuestionsMixin from '@/mixins/vehicle-questions-mixin';
import BaseFormMixin from '@/mixins/base-form-mixin.js';
import { useMainStore } from '@/store';
import widgetFields from '@/constants/cms-widget-fields';
import bailoutMessage from '@/constants/bailoutMessage';
export default {
name: 'vehicle-parts',
@ -213,8 +214,7 @@ export default {
});
},
requestCallbackBailout() {
this.mainStore.setBailout(bailoutMessage.RequestCallback());
this.$router.navigate(navigationScenarios.CLICKED_NEED_HELP_WITH_BAILOUT, this.$route);
this.$router.navigateBailout(bailoutMessage.RequestCallback());
}
}
};

View file

@ -163,16 +163,18 @@ export default {
}
},
selectedStyle(value) {
this.resetAlert();
this.mainStore.updateVehicleStyle(value);
this.mainStore.setVehicle(
this.selectedYear,
this.selectedMake,
this.selectedModel,
this.selectedStyle
).then((result) => {
this.displayNoServiceAlert = !result.data.canSafeliteService;
});
if (value) {
this.resetAlert();
this.mainStore.updateVehicleStyle(value);
this.mainStore.setVehicle(
this.selectedYear,
this.selectedMake,
this.selectedModel,
this.selectedStyle
).then((result) => {
this.displayNoServiceAlert = !result.data.canSafeliteService;
});
}
}
},
mounted() {
@ -206,77 +208,25 @@ export default {
},
navigateForward() {
this.mainStore.setVehicle().then(
() => {
if (this.mainStore.isBailout) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
this.$route
);
return;
}
this.mainStore.setVehicle().then(() => {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
},
(error) => {
this.mainStore.setBailout(bailoutMessage.vehicleYMMSLookupError(this.mainStore.vehicle.year, this.mainStore.vehicle.make, this.mainStore.vehicle.model, this.mainStore.vehicle.style, error));
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
this.$route
);
}
);
},
async updateYearValues() {
return this.mainStore.getVehicleYears().then(
(response) => response,
(error) => {
this.mainStore.setBailout(bailoutMessage.vehicleYMMSLookupError(null, null, null, null, error));
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
this.$route
);
}
);
return this.mainStore.getVehicleYears();
},
async updateMakeValues() {
return this.mainStore.getVehicleMakes().then(
(response) => response,
(error) => {
this.mainStore.setBailout(bailoutMessage.vehicleYMMSLookupError(this.mainStore.vehicle.year, null, null, null, error));
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
this.$route
);
}
);
return this.mainStore.getVehicleMakes();
},
async updateModelValues() {
return this.mainStore.getVehicleModels().then(
(response) => response,
(error) => {
this.mainStore.setBailout(bailoutMessage.vehicleYMMSLookupError(this.mainStore.vehicle.year, this.mainStore.vehicle.make, null, null, error));
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
this.$route
);
}
);
return this.mainStore.getVehicleModels();
},
async updateStyleValues() {
return this.mainStore.getVehicleStyles().then(
(response) => response,
(error) => {
this.mainStore.setBailout(bailoutMessage.vehicleYMMSLookupError(this.mainStore.vehicle.year, this.mainStore.vehicle.make, this.mainStore.vehicle.model, null, error));
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
this.$route
);
}
);
return this.mainStore.getVehicleStyles();
},
resetAlert() {
this.displayNoServiceAlert = false;

View file

@ -148,7 +148,8 @@ const mockRoute = {
};
const mockRouter = {
navigate: jest.fn(),
navigateWithSpinner: jest.fn()
navigateWithSpinner: jest.fn(),
navigateBailout: jest.fn()
};
const maska = jest.fn();
jest.mock('@/helpers/damage-helper', () => ({
@ -553,45 +554,6 @@ describe('vin-lookup.vue', () => {
);
});
});
test(
'Error in getPartsOrQuestions call => bailout true and navigate forward with CLICKED_FORWARD_WITH_BAILOUT scenario',
async () => {
const user = userEvent.setup();
mountOptions.global.stubs.vinQuestion = false;
mountOptions.data = () => ({
vinWithNonMatchingCarId: false,
isCarIdDifferentFromTheStore: false,
vin: mockValidVin,
hasBailedOut: true
});
getPartsOrQuestions.mockResponse = partsOrQuestionsErrorMockResponse;
jest.spyOn(VinLookupComponent.methods, lookupVehicleByVin.methodName)
.mockResolvedValue(lookupVehicleByVin.mockResponse);
jest.spyOn(vehicleQuestionsMixin.methods, getPartsOrQuestions.methodName)
.mockResolvedValue(getPartsOrQuestions.mockResponse);
const { container } = render(VinLookupComponent, mountOptions);
const vinInput = container.querySelector(vinInputSelector);
await user.type(vinInput, mockValidVin);
const continueButton = container.querySelector(continueButtonQuerySelector);
await user.click(continueButton);
await flushPromises();
await waitFor(() => {
expect(mockRouter.navigate).toHaveBeenCalledTimes(1);
expect(mockRouter.navigate)
.toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
mockRoute
);
});
}
);
});
});
});

View file

@ -112,8 +112,7 @@ export default {
vinWithNonMatchingCarId: vin?.length > 0 && !this.hasValidCarId(),
vin,
forwardButtonCarStyle: '',
vinPopulatedOnPageLoad: vin?.length > 0 && this.hasValidCarId(),
hasBailedOut: false
vinPopulatedOnPageLoad: vin?.length > 0 && this.hasValidCarId()
};
},
computed: {
@ -176,35 +175,37 @@ export default {
showIssLoadingModal(true);
if (this.needToLookupVehicle) {
const vehicleLookupResponse = await this.lookupVehicleByVin(this.vin);
try {
const vehicleLookupResponse = await this.lookupVehicleByVin(this.vin);
if (vehicleLookupResponse.error) {
this.activeVehicleLookupAlertType = vehicleLookupAlertTypes.NOT_FOUND;
this.mainStore.setBailout(bailoutMessage.vehicleNotFound(this.vin));
this.resetVehicleFromLookup();
// Temp solution to turn on 'disabled' style on the Continue button
// because the form itself actually passes its client-side validation.
// SSR-189 Scenario #4.
this.$refs.siteFooter.enableForwardAction();
showIssLoadingModal(false);
return;
}
if (!vehicleLookupResponse.data.canSafeliteService) {
this.activeVehicleLookupAlertType = vehicleLookupAlertTypes.NO_SERVICE;
this.resetVehicleFromLookup();
this.$refs.siteFooter.disableForwardButton();
showIssLoadingModal(false);
return;
}
// Add vin bcs the response from the service doesn't contain vin
this.vehicleFromLookup = Object.assign(
vehicleLookupResponse.data,
{
vin: this.vin
if (!vehicleLookupResponse.data.canSafeliteService) {
this.activeVehicleLookupAlertType = vehicleLookupAlertTypes.NO_SERVICE;
this.resetVehicleFromLookup();
this.$refs.siteFooter.disableForwardButton();
showIssLoadingModal(false);
return;
}
);
// Add vin bcs the response from the service doesn't contain vin
this.vehicleFromLookup = Object.assign(
vehicleLookupResponse.data,
{
vin: this.vin
}
);
} catch (e) {
if (e.status === 404) {
this.activeVehicleLookupAlertType = vehicleLookupAlertTypes.NOT_FOUND;
this.resetVehicleFromLookup();
// Temp solution to turn on 'disabled' style on the Continue button
// because the form itself actually passes its client-side validation.
// SSR-189 Scenario #4.
this.$refs.siteFooter.enableForwardAction();
showIssLoadingModal(false);
return;
}
throw e;
}
}
if (this.needToLookupVehicle && this.isCarIdDifferentFromTheStore) {
@ -259,34 +260,15 @@ export default {
}
const partsOrQuestionsResponse = await this.getPartsOrQuestions();
if (partsOrQuestionsResponse.error) {
this.mainStore.setBailout(bailoutMessage.PartsServiceError(partsOrQuestionsResponse.error.data));
window.console.error('Error on retrieving PartsOrQuestions');
this.hasBailedOut = true;
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
this.$route
);
}
// Comes from vehicleQuestionsMixin.navigateForward()
if (!this.hasBailedOut) {
await this.navigateForward(
partsOrQuestionsResponse.data.partsOrQuestions,
this
);
}
await this.navigateForward(
partsOrQuestionsResponse.data.partsOrQuestions,
this
);
},
async lookupVehicleByVin(vin) {
try {
return await this.mainStore.lookupVehicleByVin(vin);
} catch (responseError) {
return {
error: {
status: responseError.status
}
};
}
return this.mainStore.lookupVehicleByVin(vin);
},
resetActiveAlert() {
this.activeVehicleLookupAlertType = null;

View file

@ -72,7 +72,8 @@ function setupMocks({
const mockDataMountOptions = {
...mountOptionsMockData,
router: {
navigate: jest.fn()
navigate: jest.fn(),
navigateBailout: jest.fn()
}
};
@ -166,7 +167,7 @@ describe('navigation', () => {
const { wrapper } = setupMocks({});
wrapper.vm.mainStore.getDuplicateReferrals = jest.fn().mockImplementation(() => Promise.resolve({}));
wrapper.vm.mainStore.applicationUser.duplicateOrders = [{ test: 'a' }];
wrapper.vm.mainStore.validateZip = jest.fn().mockImplementation(() => Promise.reject());
wrapper.vm.mainStore.validateZip = jest.fn().mockImplementation(() => Promise.reject({ isAxiosError: false }));
// Act
await wrapper.vm.forwardButtonAction();
@ -316,10 +317,14 @@ describe('navigation', () => {
}));
// Act
await wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
expect.assertions(1);
try {
await wrapper.vm.forwardButtonAction();
} catch (e) {
expect(e).toMatch(error);
}
});
});
});

View file

@ -177,6 +177,7 @@ import { getPropertyCaseInsensitive } from '@/helpers/object-helper';
import { saveSession } from '@/helpers/order-helper';
import { getISSCookie } from '@/helpers/cookie-helper.js';
import bailoutMessage from '@/constants/bailoutMessage';
import showIssLoadingModal from '@/helpers/loading-modal-helper';
// define validation rules
defineRule(
@ -303,54 +304,46 @@ export default {
},
methods: {
async forwardButtonAction() {
try {
this.mainStore.updatePolicyData(this.welcomePageModel);
const promises = [];
promises.push(this.configureZip().then(async () => await this.mainStore.getBillToInfo()));
this.mainStore.updatePolicyData(this.welcomePageModel);
// Clear duplicate orders if navigating away from the welcome page after visiting duplicate check page.
if (this.mainStore.order.visitedDuplicateCheckPage) {
this.mainStore.clearDuplicateOrders();
}
// Skip duplicate check if loaded from cookie or already visited duplicate check page.
if (!this.mainStore.order.loadedFromCookie && !this.mainStore.order.visitedDuplicateCheckPage) {
promises.push(this.mainStore.getDuplicateReferrals());
}
promises.push(this.mainStore.getCoveragePolicyInfo());
await Promise.allSettled(promises);
} catch (e) {
console.error(e);
// TODO: Bailout?
} finally {
if (!this.displayInvalidZipAlert) {
await saveSession({ shouldAwaitSaveSessionQueue: true })
.catch((error) => {
this.mainStore.setBailout(bailoutMessage.saveSessionError(error.data));
})
.finally(() => this.navigateForward());
}
// We want to await this separately. It's quick and if this has an invalid zip we don't want to be waiting on the slower API calls
await this.configureZip();
if (this.displayInvalidZipAlert) {
showIssLoadingModal(false);
return;
}
// Clear duplicate orders if navigating away from the welcome page after visiting duplicate check page.
if (this.mainStore.order.visitedDuplicateCheckPage) {
this.mainStore.clearDuplicateOrders();
}
const promises = [
// Suppress error from API call we can try again later in the flow
this.mainStore.getBillToInfo()
];
// Skip duplicate check if loaded from cookie or already visited duplicate check page.
if (!this.mainStore.order.loadedFromCookie && !this.mainStore.order.visitedDuplicateCheckPage) {
promises.push(this.mainStore.getDuplicateReferrals());
}
promises.push(this.mainStore.getCoveragePolicyInfo());
await Promise.all(promises);
await saveSession({ shouldAwaitSaveSessionQueue: true, bailoutOnError: true })
this.navigateForward();
},
async configureZip() {
try {
this.displayInvalidZipAlert = false;
await this.mainStore.validateZip({ zip: this.welcomePageModel.policyZipCode });
return Promise.resolve();
} catch (e) {
if (e.isAxiosError) {
throw e;
}
this.displayInvalidZipAlert = true;
return Promise.reject(e);
}
},
navigateForward() {
if (this.mainStore.isBailout) {
this.$router.navigate(
this.navigationScenarios.SAVE_SESSION_FAILED,
this.$route,
{},
{ [routerParams.SKIP_SAVE_SESSION]: true }
);
} else if ((this.mainStore.applicationUser.duplicateOrders?.length > 0 ?? false)
if ((this.mainStore.applicationUser.duplicateOrders?.length > 0 ?? false)
&& !this.answeredContinueModal) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_DUPLICATES,
@ -445,12 +438,7 @@ export default {
.then((response) => {
this.answeredContinueModal = true;
if (response && !response.provider?.isSafeliteProvider) {
this.mainStore.setBailout(bailoutMessage.SafeliteNotTheProvider());
this.hasBailedOut = true;
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
this.$route
);
this.$router.navigateBailout(bailoutMessage.SafeliteNotTheProvider());
} else {
this.updateWelcomePageModel(response);
}

View file

@ -48,7 +48,7 @@ function getPageName(vm) {
// Vue Error Handling
vueApp.config.errorHandler = (err, vm, info) => {
const pageName = getPageName(vm);
global.$logger.logError(`Page Name - ${pageName} - ${info}: ${err.message}\n${err.stack}`);
global.$logger.logError(`[${pageName}] ${info}`, err);
if (applicationConfig.BAILOUT_ON_APPLICATION_ERROR) {
router.navigateBailout(bailoutMessage.applicationError(`[${pageName}] ${info}: ${err.message}\n${err.stack}`));
}
@ -56,7 +56,7 @@ vueApp.config.errorHandler = (err, vm, info) => {
// Vue Router Error Handling
router.onError((err) => {
global.$logger.logError(err.message, err.cause);
global.$logger.logError('Router Error:', err);
if (applicationConfig.BAILOUT_ON_ROUTER_ERROR) {
router.navigateBailout(bailoutMessage.routerError(`${err.message}\n${err.stack}`));
}

View file

@ -353,16 +353,7 @@ export default {
},
async getPartsOrQuestions() {
try {
return await useMainStore().getPartsOrQuestions();
} catch (responseError) {
return {
error: {
status: responseError.status,
data: responseError.data
}
};
}
return useMainStore().getPartsOrQuestions();
},
// Can't use `this` because navigateForward is also called from vin-pages-mixin

View file

@ -25,100 +25,95 @@ const routes = [
path: '/',
name: 'root',
async beforeEnter(to, from, next) {
try {
const issPageToUse = !to.query.issPage ? issPageValues.WELCOME_PAGE : to.query.issPage;
const fromQueryPage = from.query?.issPage;
const issPageToUse = !to.query.issPage ? issPageValues.WELCOME_PAGE : to.query.issPage;
const fromQueryPage = from.query?.issPage;
if ((issPageToUse === issPageValues.ACCESS_DENIED
if ((issPageToUse === issPageValues.ACCESS_DENIED
|| (issPageToUse !== issPageValues.ENTRY_PAGE && !useMainStore().issConfig.parentAccountNumber))
&& process.env.VUE_APP_CURRENT_ENVIRONMENT !== 'Localhost'
) {
return await GoToAccessIsDenied(next);
&& process.env.VUE_APP_CURRENT_ENVIRONMENT !== 'Localhost'
) {
return await GoToAccessIsDenied(next);
}
// Do not run these for the main entry page - as it is not part of the user flow.
if (issPageToUse !== issPageValues.ENTRY_PAGE) {
if (analyticsMixin.methods.noSession()) {
await analyticsMixin.methods.initSession();
} else {
updateSessionIdCookie();
}
// Do not run these for the main entry page - as it is not part of the user flow.
if (issPageToUse !== issPageValues.ENTRY_PAGE) {
if (analyticsMixin.methods.noSession()) {
await analyticsMixin.methods.initSession();
} else {
updateSessionIdCookie();
}
await runExperiments(issPageToUse); // fmg has this further down
}
await runExperiments(issPageToUse); // fmg has this further down
// Intercept all navigation if a submitted order exists in storage
if (useMainStore().hasSubmittedOrder()) {
if (to.query.issPage !== issPageValues.ENTRY_PAGE) {
return await GoToConfirmationPage(next, useMainStore().getSubmittedOrder());
}
}
// If the saved session has timed out, clear the session, execute 404 logic.
if (getISSCookie() !== null && !isSavedSessionStillActive()) {
// await baseMixin.methods.dispatchStoreAction(storeActions.RESET_STATE);
await GoToStartOn404(next);
}
// Process ISS cookie.
// Skip if Entry Page or Refreshing Welcome page
if (issPageToUse !== issPageValues.ENTRY_PAGE
&& !(issPageToUse === issPageValues.WELCOME_PAGE && fromQueryPage === undefined)) {
updateOrCreateISSCookie();
}
if (router.hasRoute(issPageToUse)) {
// Since our route is already in scope, we can grab the component and call the arePagePrerequisitesValid function.
let component = router.getRoutes().filter((x) => x.name === issPageToUse)[0].components;
// If the component hasn't been loaded fully, load it before we check prerequisites.
if (component.default.methods === undefined) {
component = await component.default();
}
// Intercept all navigation if a submitted order exists in storage
if (useMainStore().hasSubmittedOrder()) {
if (to.query.issPage !== issPageValues.ENTRY_PAGE) {
return await GoToConfirmationPage(next, useMainStore().getSubmittedOrder());
}
}
// If the saved session has timed out, clear the session, execute 404 logic.
if (getISSCookie() !== null && !isSavedSessionStillActive()) {
// await baseMixin.methods.dispatchStoreAction(storeActions.RESET_STATE);
if (!arePagePrerequisitesValid(component)) {
await GoToStartOn404(next);
}
// Process ISS cookie.
// Skip if Entry Page or Refreshing Welcome page
if (issPageToUse !== issPageValues.ENTRY_PAGE
&& !(issPageToUse === issPageValues.WELCOME_PAGE && fromQueryPage === undefined)) {
updateOrCreateISSCookie();
}
if (router.hasRoute(issPageToUse)) {
// Since our route is already in scope, we can grab the component and call the arePagePrerequisitesValid function.
let component = router.getRoutes().filter((x) => x.name === issPageToUse)[0].components;
// If the component hasn't been loaded fully, load it before we check prerequisites.
if (component.default.methods === undefined) {
component = await component.default();
}
if (!arePagePrerequisitesValid(component)) {
await GoToStartOn404(next);
}
return next({ name: issPageToUse, query: to.query, params: to.params });
}
const routeData = await GetRouteInfoFromPageName(issPageToUse);
if (routeData[0].name.toLowerCase() === 'error') {
throw new Error('Page not found!');
}
// Add our dynamic route.
router.addRoute({
path: routeData[0].path, // Always the same path, because we control it with query strings.
name: routeData[0].name,
component: routeData[0].component
});
// Call the next components arePagePrerequisitesValid method before load.
// If it returns false, use the 404 logic.
const nextComponent = await router
.getRoutes()
.filter((x) => x.name === routeData[0].name)[0]
.components.default();
if (!arePagePrerequisitesValid(nextComponent)) {
const tempMsgCopy = 'Pre Requisites failed, need to handle. Default is Welcome Page.';
const tempMsgHeadline = `${issPageToUse}: pre-req failed...`;
await GoToStartOn404(next, tempMsgCopy, tempMsgHeadline);
}
// Assign current query string parameters, as well as our issPage one.
next({
name: routeData[0].name,
query: Object.assign(to.query, { issPage: routeData[0].name }),
params: to.params
});
} catch (error) {
window.console.warn(error);
await GoToStartOn404(next);
return next({name: issPageToUse, query: to.query, params: to.params});
}
const routeData = await GetRouteInfoFromPageName(issPageToUse);
if (routeData[0].name.toLowerCase() === 'error') {
throw new Error('Page not found!');
}
// Add our dynamic route.
router.addRoute({
path: routeData[0].path, // Always the same path, because we control it with query strings.
name: routeData[0].name,
component: routeData[0].component
});
// Call the next components arePagePrerequisitesValid method before load.
// If it returns false, use the 404 logic.
const nextComponent = await router
.getRoutes()
.filter((x) => x.name === routeData[0].name)[0]
.components.default();
if (!arePagePrerequisitesValid(nextComponent)) {
const tempMsgCopy = 'Pre Requisites failed, need to handle. Default is Welcome Page.';
const tempMsgHeadline = `${issPageToUse}: pre-req failed...`;
await GoToStartOn404(next, tempMsgCopy, tempMsgHeadline);
}
// Assign current query string parameters, as well as our issPage one.
next({
name: routeData[0].name,
query: Object.assign(to.query, {issPage: routeData[0].name}),
params: to.params
});
return null;
}
}
@ -177,7 +172,7 @@ router.afterEach(async (to, from) => {
const skipSaveSession = !!router.options.history.state[routerParams.SKIP_SAVE_SESSION];
if (from.name !== undefined && !skipSaveSession && !store.hasSubmittedOrder()) {
await saveSession({});
await saveSession({ bailoutOnError: from.name === issPageValues.ENTRY_PAGE});
}
if (to.query.issPage !== issPageValues.ENTRY_PAGE) {
@ -350,17 +345,12 @@ router.navigateBailout = (bailoutData = null) => {
// Get navigation map depending on the scenario and the current 'page' you're on.
function getNavigationMap(scenario, currentRoute) {
const issPageValue = currentRoute.query.issPage;
try {
const matchedQueryValue = routingTable(useMainStore())
.filter((item) => item.issPageValue === issPageValue && item.maps.filter((map) => map.scenario === scenario).length > 0);
const matchedQueryValue = routingTable(useMainStore())
.filter((item) => item.issPageValue === issPageValue && item.maps.filter((map) => map.scenario === scenario).length > 0);
const maps = matchedQueryValue ? matchedQueryValue.map((m) => m.maps.filter((map) => map.scenario === scenario))[0] : undefined;
const maps = matchedQueryValue ? matchedQueryValue.map((m) => m.maps.filter((map) => map.scenario === scenario))[0] : undefined;
return maps ? maps.filter((x) => x.filter === true || x.filter === undefined)[0] : undefined;
} catch (e) {
window.console.error(e);
return undefined;
}
return maps ? maps.filter((x) => x.filter === true || x.filter === undefined)[0] : undefined;
}
async function GoToAccessIsDenied(next) {

View file

@ -12,7 +12,6 @@ const navigationScenarios = Object.freeze({
CLICKED_FORWARD_POLICY_UNVERIFIED: 'CLICKED_FORWARD_POLICY_UNVERIFIED',
CLICKED_FORWARD_POLICY_VERIFIED_NO_VEHICLES: 'CLICKED_FORWARD_POLICY_VERIFIED_NO_VEHICLES',
CLICKED_FORWARD_POLICY_VERIFIED_WITH_VEHICLES: 'CLICKED_FORWARD_POLICY_VERIFIED_WITH_VEHICLES',
SAVE_SESSION_FAILED: 'SAVE_SESSION_FAILED',
// Duplicate Check
CLICKED_FORWARD_LOADED_DUPLICATE_WITH_POLICY_VEHICLE: 'CLICKED_FORWARD_LOADED_DUPLICATE_WITH_POLICY_VEHICLE',
@ -70,11 +69,8 @@ const navigationScenarios = Object.freeze({
// Coverage Statement
CLICKED_BACK_WITH_REPAIR: 'CLICKED_BACK_WITH_REPAIR',
CLICKED_FORWARD_WITH_INVALID_STATE: 'CLICKED_FORWARD_WITH_INVALID_STATE',
PRICING_LOOKUP_ERROR: 'PRICING_LOOKUP_ERROR',
// TPA Search
CLICKED_NEED_HELP: 'CLICKED_NEED_HELP',
CLICKED_FORWARD_WITH_SAFELITE_SHOP: 'CLICKED_FORWARD_WITH_SAFELITE_SHOP',
CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP: 'CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP',
@ -88,7 +84,6 @@ const navigationScenarios = Object.freeze({
// Provider Preference
CLICKED_FORWARD_WITH_SAFELITE: 'CLICKED_FORWARD_WITH_SAFELITE',
CLICKED_FORWARD_WITH_TPA_ENABLED: 'CLICKED_FORWARD_WITH_TPA_ENABLED',
CLICKED_FORWARD_WITH_TPA_DISABLED: 'CLICKED_FORWARD_WITH_TPA_DISABLED',
CLICKED_FORWARD_WITH_POLICY_AND_VEHICLES: 'CLICKED_FORWARD_WITH_POLICY_AND_VEHICLES',
// Service Package
@ -115,8 +110,6 @@ const navigationScenarios = Object.freeze({
EDIT_WIPERS: 'EDIT_WIPERS',
// Bailout
CLICKED_FORWARD_WITH_BAILOUT: 'CLICKED_FORWARD_WITH_BAILOUT',
CLICKED_NEED_HELP_WITH_BAILOUT: 'CLICKED_NEED_HELP_WITH_BAILOUT',
BAILOUT: 'BAILOUT'
});

View file

@ -14,10 +14,6 @@ const routingTable = () => [
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
destinationIssPageValue: issPageValues.BAILOUT_PAGE
}
]
},
@ -55,10 +51,6 @@ const routingTable = () => [
{
scenario: navigationScenarios.CLICKED_FORWARD_WITHOUT_VIN,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
destinationIssPageValue: issPageValues.BAILOUT_PAGE
}
]
},
@ -141,14 +133,6 @@ const routingTable = () => [
{
scenario: issPageValues.VEHICLE_LOOKUP,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP
},
{
scenario: issPageValues.BAILOUT_PAGE, // TODO is this a bug
destinationIssPageValue: issPageValues.BAILOUT_PAGE
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
destinationIssPageValue: issPageValues.BAILOUT_PAGE
}
]
},
@ -215,10 +199,6 @@ const routingTable = () => [
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT
},
{
scenario: navigationScenarios.CLICKED_NEED_HELP_WITH_BAILOUT,
destinationIssPageValue: issPageValues.BAILOUT_PAGE
}
]
},
@ -256,10 +236,6 @@ const routingTable = () => [
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT
},
{
scenario: navigationScenarios.CLICKED_NEED_HELP_WITH_BAILOUT,
destinationIssPageValue: issPageValues.BAILOUT_PAGE
}
]
},
@ -293,10 +269,6 @@ const routingTable = () => [
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT
},
{
scenario: navigationScenarios.CLICKED_NEED_HELP_WITH_BAILOUT,
destinationIssPageValue: issPageValues.BAILOUT_PAGE
}
]
},
@ -455,14 +427,6 @@ const routingTable = () => [
{
scenario: navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED_WITH_VEHICLES,
destinationIssPageValue: issPageValues.POLICY_VEHICLES
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
destinationIssPageValue: issPageValues.BAILOUT_PAGE
},
{
scenario: navigationScenarios.SAVE_SESSION_FAILED,
destinationIssPageValue: issPageValues.BAILOUT_PAGE
}
]
},
@ -539,10 +503,6 @@ const routingTable = () => [
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAR_ID_NOT_FOUND,
destinationIssPageValue: issPageValues.VIN_LOOKUP
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
destinationIssPageValue: issPageValues.BAILOUT_PAGE
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_ENDORSEMENTS,
destinationIssPageValue: issPageValues.POLICY_ENDORSEMENTS
@ -591,18 +551,6 @@ const routingTable = () => [
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE,
destinationIssPageValue: issPageValues.SCHEDULE_PAGE
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP,
destinationIssPageValue: issPageValues.BAILOUT_PAGE
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_INVALID_STATE,
destinationIssPageValue: issPageValues.BAILOUT_PAGE
},
{
scenario: navigationScenarios.PRICING_LOOKUP_ERROR,
destinationIssPageValue: issPageValues.BAILOUT_PAGE
}
]
},
@ -620,10 +568,6 @@ const routingTable = () => [
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_TPA_ENABLED,
destinationIssPageValue: issPageValues.TPA_SEARCH
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_TPA_DISABLED,
destinationIssPageValue: issPageValues.BAILOUT_PAGE
}
]
},
@ -693,10 +637,6 @@ const routingTable = () => [
scenario: navigationScenarios.CLICKED_PAY_NOW,
destinationIssPageValue: issPageValues.PAYMENT_PAGE
},
{
scenario: navigationScenarios.SAVE_SESSION_FAILED,
destinationIssPageValue: issPageValues.BAILOUT_PAGE
},
{
scenario: navigationScenarios.EDIT_SERVICE_LOCATION,
destinationIssPageValue: issPageValues.SCHEDULE_PAGE
@ -784,10 +724,6 @@ const routingTable = () => [
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.TPA_CONFIRMATION
},
{
scenario: navigationScenarios.SAVE_SESSION_FAILED,
destinationIssPageValue: issPageValues.BAILOUT_PAGE
}
]
},
@ -814,10 +750,6 @@ const routingTable = () => [
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP,
destinationIssPageValue: issPageValues.TPA_SUBMIT
},
{
scenario: navigationScenarios.CLICKED_NEED_HELP,
destinationIssPageValue: issPageValues.BAILOUT_PAGE
}
]
},

View file

@ -4,6 +4,8 @@ import issPageValues from '@/router/router-constants/issPage-values';
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from '@/App.vue';
import bailoutMessage from '@/constants/bailoutMessage';
import { useMainStore } from '@/store';
describe('Router', () => {
beforeAll(() => {
@ -37,18 +39,15 @@ describe('Router', () => {
expect(router.push.mock.calls[0][0].state).toBe(parameters);
});
it('Should route from CAPABILITY_QUESTIONS to BAILOUT_PAGE on CLICKED_NEED_HELP_WITH_BAILOUT', () => {
const scenario = navigationScenarios.CLICKED_NEED_HELP_WITH_BAILOUT;
const currentRoute = { query: { issPage: issPageValues.CAPABILITY_QUESTIONS } };
it('Should set bailout and navigate to bailout page when calling navigateBailout', () => {
// Arrange
router.push = jest.fn();
// Act
router.navigate(scenario, currentRoute);
router.navigateBailout(bailoutMessage.unknown({}));
// Assert
expect(router.push).toHaveBeenCalled();
expect(router.push.mock.calls[0][0].query.issPage).toBe(issPageValues.BAILOUT_PAGE);
expect(useMainStore().isBailout).toBeTruthy();
});
});

View file

@ -1,7 +1,6 @@
import { defineStore } from 'pinia';
import applicationConfig from '@/constants/application-config';
import bailoutCode from '@/constants/bailoutCode';
import bailoutMessage from '@/constants/bailoutMessage';
import coverageStatuses from '@/constants/coverage-statuses';
import coverageType from '@/constants/coverage-type';
import damageLocationsSelected from '@/constants/damage-locations-selected';
@ -22,14 +21,11 @@ import {
noCoverageForSelectedVehicle,
repairWaivedForSelectedVehicle
} from '@/helpers/policy-vehicle-helper';
import {
buildURLSearchParams,
getPartNumbersListForQueryString,
getTaxLineItemQueryString
} from '@/helpers/querystring-helper';
import { buildURLSearchParams, getPartNumbersListForQueryString } from '@/helpers/querystring-helper';
import { getTopLevelGlassPartsWithRecal } from '@/helpers/recal-helper';
import { getDateForSavedSessionTimeout } from '@/helpers/session-helper';
import issPageValues from '@/router/router-constants/issPage-values';
import CoverageStatuses from '@/constants/coverage-statuses';
const storeId = 'main';
@ -533,20 +529,15 @@ export const useMainStore = defineStore({
payload: {}
});
},
getIsVinbyAddressPermissible() {
async getIsVinbyAddressPermissible() {
try {
const response = globalMethods.callHttpClient({
return globalMethods.callHttpClient({
method: endpoints.IsVinbyAddressPermissible.method,
endpoint: `${endpoints.IsVinbyAddressPermissible.url}?state=${this.order.customer.address.state}`,
payload: {}
});
return response;
} catch (responseError) {
return {
error: {
status: responseError.status
}
};
});
} catch (e) {
return false;
}
},
async getCoveragePolicyInfo() {
@ -555,7 +546,7 @@ export const useMainStore = defineStore({
if (!issConfig.isCoverageEnabled || applicationUser.coverageLookupAttempts > 10) {
this.updateCoverageType(coverageType.NONE);
return Promise.resolve();
return;
}
this.applicationUser.coverageAttempts += 1;
@ -571,7 +562,8 @@ export const useMainStore = defineStore({
dateOfLoss: policy.dateOfLoss,
zipCode: policy.policyZipCode,
referralCorrelationId: order.referralCorrelationId
}
},
bailoutOnError: false
});
const responsePolicy = response?.data?.policies?.[0];
@ -600,10 +592,8 @@ export const useMainStore = defineStore({
} else {
this.updateCoverageType(coverageType.NONE);
}
return Promise.resolve();
} catch (e) {
this.updateCoverageType(coverageType.NONE);
return Promise.reject(e);
}
},
clearDuplicateOrders() {
@ -621,82 +611,81 @@ export const useMainStore = defineStore({
updateIsItacOptimized(isItacOptimized) {
this.order.insuranceCoverage.isItacOptimized = isItacOptimized || false;
},
registerClaim() {
async registerClaim() {
const nonNumberCharRegex = /[^0-9]/g;
const { order, isITAC } = this;
return new Promise((resolve, reject) => {
globalMethods.callHttpClient({
const { isITAC } = this;
try {
const response = await globalMethods.callHttpClient({
method: endpoints.RegisterClaim.method,
endpoint: endpoints.RegisterClaim.url,
payload:
{
referralCorrelationId: this.order.referralCorrelationId,
accountNumber: this.order.parentAccountNumber?.toString() ?? '',
policyData: this.order.policy.policyData,
isItac: isITAC,
insured: {
firstName: this.order.customer.firstName,
lastName: this.order.customer.lastName,
address: {
addressLine1: this.order.customer.address.streetAddress,
addressLine2: this.order.customer.address.streetAddress2,
city: this.order.customer.address.city,
state: this.order.customer.address.state,
zipCode: this.order.customer.address.zipCode,
country: 'US' // TODO set from store
{
referralCorrelationId: this.order.referralCorrelationId,
accountNumber: this.order.parentAccountNumber?.toString() ?? '',
policyData: this.order.policy.policyData,
isItac: isITAC,
insured: {
firstName: this.order.customer.firstName,
lastName: this.order.customer.lastName,
address: {
addressLine1: this.order.customer.address.streetAddress,
addressLine2: this.order.customer.address.streetAddress2,
city: this.order.customer.address.city,
state: this.order.customer.address.state,
zipCode: this.order.customer.address.zipCode,
country: 'US' // TODO set from store
},
homePhone: {
number: this.contactInfo.homePhone?.replaceAll(nonNumberCharRegex, '') ?? ''
}
},
homePhone: {
number: this.contactInfo.homePhone?.replaceAll(nonNumberCharRegex, '') ?? ''
driver: {
firstName: this.order.customer.firstName,
lastName: this.order.customer.lastName
},
caller: {
homePhone: {}
},
policyInfo: {
policyNumber: this.order.policy.policyNumber,
safelitePolicy: {
policies: []
},
actualDeductible: this.currentDeductible.toString() ?? ''
},
lossInfo: {
dateOfLoss: this.order.policy.dateOfLoss,
location: {
city: this.order.policy.damageCity,
state: this.order.policy.damageState,
country: 'US' // TODO set from store
},
vehicle: {
id: this.order.vehicle.policyVehicleId?.toString() ?? '',
year: this.order.vehicle.year?.toString() ?? '',
make: this.order.vehicle.make,
model: this.order.vehicle.model,
vin: this.order.vehicle.vin
},
cause: this.order.policy.damageCause,
damageDescription: this.order.policy.damageCause
}
},
driver: {
firstName: this.order.customer.firstName,
lastName: this.order.customer.lastName
},
caller: {
homePhone: {}
},
policyInfo: {
policyNumber: this.order.policy.policyNumber,
safelitePolicy: {
policies: []
},
actualDeductible: this.currentDeductible.toString() ?? ''
},
lossInfo: {
dateOfLoss: this.order.policy.dateOfLoss,
location: {
city: this.order.policy.damageCity,
state: this.order.policy.damageState,
country: 'US' // TODO set from store
},
vehicle: {
id: this.order.vehicle.policyVehicleId?.toString() ?? '',
year: this.order.vehicle.year?.toString() ?? '',
make: this.order.vehicle.make,
model: this.order.vehicle.model,
vin: this.order.vehicle.vin
},
cause: this.order.policy.damageCause,
damageDescription: this.order.policy.damageCause
}
}
}).then((response) => {
this.order.insuranceCoverage.claimNumber = response.data.claimNumber;
if (response.data.isSuccess) {
this.updateCoverageStatus(coverageStatuses.VERIFIED);
} else {
this.updateCoverageStatus(coverageStatuses.NO_COVERAGE);
}
return resolve(response);
}, (error) => {
this.updateCoverageStatus(coverageStatuses.NO_COVERAGE);
this.order.insuranceCoverage.claimNumber = null;
return reject(error);
bailoutOnError: false
});
});
this.order.insuranceCoverage.claimNumber = response.data.claimNumber;
if (response.data.isSuccess) {
this.updateCoverageStatus(coverageStatuses.VERIFIED);
} else {
this.updateCoverageStatus(coverageStatuses.NO_COVERAGE);
}
} catch (e) {
this.updateCoverageStatus(coverageStatuses.NO_COVERAGE);
this.order.insuranceCoverage.claimNumber = null;
}
},
getFinalDeductible() {
async getFinalDeductible() {
const endorsementAnswersForPayload = [];
const endorsementAnswers = this.order.policy.endorsementQuestionAnswers;
if (endorsementAnswers) {
@ -712,9 +701,9 @@ export const useMainStore = defineStore({
glassInformation?.forEach((glassPiece) => {
manualGlassNamesArray.push(glassPiece?.glassLocation?.toUpperCase());
});
return new Promise((resolve, reject) => {
globalMethods.callHttpClient({
try {
const r = await globalMethods.callHttpClient({
method: endpoints.FinalDeductible.method,
endpoint: endpoints.FinalDeductible.url,
payload: {
@ -736,16 +725,18 @@ export const useMainStore = defineStore({
vehicleVin: this.order.vehicle.vin,
policyData: this.order.policy.policyData,
isItac: this.isITAC
}
}).then((r) => {
this.order.policy.policyData = r.data.policyData;
this.updateDeductible(r.data);
return resolve(r);
}).catch((error) => reject(error));
});
},
bailoutOnError: false
});
this.order.policy.policyData = r.data.policyData;
this.updateDeductible(r.data);
return r;
} catch (e) {
this.updateCoverageStatus(CoverageStatuses.PENDING);
}
},
getDuplicateReferrals() {
async getDuplicateReferrals() {
const params = new URLSearchParams({
parentAccountNumber: this.order.parentAccountNumber,
customerPhoneNumber: this.order.contactInfo.servicePhone,
@ -754,18 +745,16 @@ export const useMainStore = defineStore({
dateOfLoss: this.order.policy.dateOfLoss
});
return new Promise((resolve, reject) => {
globalMethods.callHttpClient({
try {
const duplicates = await globalMethods.callHttpClient({
method: endpoints.DuplicateSearch.method,
endpoint: `${endpoints.DuplicateSearch.url}?${params.toString()}`
}).then((r) => {
this.applicationUser.duplicateOrders = r.data ?? [];
return resolve(r.data);
}).catch((error) => {
this.applicationUser.duplicateOrders = [];
return reject(error);
endpoint: `${endpoints.DuplicateSearch.url}?${params.toString()}`,
bailoutOnError: false
});
});
this.applicationUser.duplicateOrders = duplicates.data ?? [];
} catch (e) {
this.applicationUser.duplicateOrders = [];
}
},
async lookupVinByPlate(licensePlate, licenseState) {
try {
@ -990,7 +979,8 @@ export const useMainStore = defineStore({
return globalMethods.callHttpClient({
method: endpoints.GetShopTimeSlots.method,
endpoint: endpoints.GetShopTimeSlots.url,
payload
payload,
bailoutOnError: false
});
},
async getWipers() {
@ -1218,9 +1208,6 @@ export const useMainStore = defineStore({
IsOEMRequest: this.hasOemEndorsement
}
}
}).catch((error) => {
console.error(error);
throw error;
});
const { lineItems, serverData, isItac, primaryBillToNumber, partsWerePriced, isItacOptimized } = response.data;
@ -1360,7 +1347,8 @@ export const useMainStore = defineStore({
endpoint: endpoints.LookupVehicleByVin.url,
payload: {
vin
}
},
bailoutOnError: false
});
},
@ -1392,7 +1380,7 @@ export const useMainStore = defineStore({
this.applicationUser.crmCustomerId = response.crmCustomerId.toString();
},
saveSession({ submitAfterSave, createWorkOrderNumberForPIA }) {
saveSession({ submitAfterSave, createWorkOrderNumberForPIA, bailoutOnError }) {
const { vehicle, damage, policy, customer, contactInfo, payment,
lineItems, serviceLocation, schedule, insuranceCoverage } = this.order;
@ -1544,7 +1532,7 @@ export const useMainStore = defineStore({
method: endpoints.SaveSession.method,
endpoint: endpoints.SaveSession.url,
payload,
bailoutOnError: false
bailoutOnError
}).then((response) => {
if (loadedFromDupeCheck) {
this.order.loadedSessionClearedPreviousData = true;
@ -2461,6 +2449,7 @@ export const useMainStore = defineStore({
populateInitialState(forceReset) {
if (!sessionStorage.getItem(storeId) || forceReset) {
this.$state = getDefaultState();
this.resetSubmittedOrder();
}
},
@ -2699,34 +2688,26 @@ export const useMainStore = defineStore({
async getBillToInfo(componentProviderNumber = null) {
const { order, issConfig } = this;
try {
const params = new URLSearchParams({
parentAccountNumber: order.parentAccountNumber.toString(),
providerNumber: componentProviderNumber || this.providerNumber,
typeOfClaim: 'GLASS ONLY',
lineOfBusiness: 'PERSONAL',
isItac: this.isITAC
});
const params = new URLSearchParams({
parentAccountNumber: order.parentAccountNumber.toString(),
providerNumber: componentProviderNumber || this.providerNumber,
typeOfClaim: 'GLASS ONLY',
lineOfBusiness: 'PERSONAL',
isItac: this.isITAC
});
const response = await globalMethods.callHttpClient({
method: endpoints.GetBillToInfo.method,
endpoint: `${endpoints.GetBillToInfo.url}?${params.toString()}`
});
const response = await globalMethods.callHttpClient({
method: endpoints.GetBillToInfo.method,
endpoint: `${endpoints.GetBillToInfo.url}?${params.toString()}`,
bailoutOnError: false
});
const billToInfo = response.data;
if (billToInfo != null) {
issConfig.billToAccountNumber = billToInfo.toString();
return Promise.resolve();
}
return Promise.reject(new Error('Invalid billToInfo'));
} catch (e) {
return Promise.reject(e);
}
const billToInfo = response.data;
issConfig.billToAccountNumber = billToInfo.toString();
},
async validateClientTag(clientTag) {
return await globalMethods.callHttpClient({
return globalMethods.callHttpClient({
method: endpoints.ValidateClientTag.method,
endpoint: `${endpoints.ValidateClientTag.url}/${clientTag}`
});

View file

@ -459,9 +459,9 @@ describe('Store', () => {
expect(store.order.insuranceCoverage.claimNumber).not.toBeNull();
});
it('Call to client returns exception, resulting in object with error property being returned', async () => {
it('Call to client returns exception, and no error is returned from registerClaim method', async () => {
// Arrange
expect.assertions(4);
expect.assertions(3);
const error = 'register claim error';
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.reject(error));
store.order.insuranceCoverage.coverageStatus = coverageStatuses.PENDING;
@ -1494,8 +1494,8 @@ describe('Store', () => {
// Assert
expect(globalMethods.callHttpClient).toHaveBeenCalled();
});
it('api call throws exception => insuranceCoverage.coverageType is NONE', async () => {
expect.assertions(3);
it('api call throws exception => no error is thrown from getCoveragePolicyInfo and insuranceCoverage.coverageType is NONE', async () => {
expect.assertions(2);
const error = 'get coverage policy info error';
store.issConfig.isCoverageEnabled = true;
store.applicationUser.coverageLookupAttempts = 0;
@ -1567,9 +1567,9 @@ describe('Store', () => {
expect(globalMethods.callHttpClient).toHaveBeenCalled();
expect(store.applicationUser.duplicateOrders).toEqual(expected);
});
it('Call to client returns exception => object with error property returned and duplicateReferrals set to []', async () => {
it('Call to client returns exception => object with error property returned and duplicateReferrals set to [] and no error is returned from getDuplicateReferrals', async () => {
// Arrange
expect.assertions(3);
expect.assertions(2);
const error = 'get duplicate referrals error';
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.reject(error));
@ -1807,7 +1807,8 @@ describe('Store', () => {
vehicleVin,
policyData,
isItac: store.isITAC
})
}),
bailoutOnError: false
}));
});
it('only "Yes" endorsement answers are added to payload', () => {
@ -1912,14 +1913,15 @@ describe('Store', () => {
vehicleVin,
policyData,
isItac: store.isITAC
})
}),
bailoutOnError: false
}));
});
});
describe('unsuccessful api call', () => {
it('api call throws exception', async () => {
it('api call throws exception. no error from getFinalDeductible', async () => {
// Arrange
expect.assertions(2);
expect.assertions(1);
const error = 'final deductible error';
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.reject(error));
@ -2078,8 +2080,8 @@ describe('Store', () => {
expect(result.data.shopProviders[1]).toBe(provider2);
});
});
it('api call throws exception => coverageType none', async () => {
expect.assertions(3);
it('api call throws exception => coverageType none and no error from getCoveragePolicyInfo', async () => {
expect.assertions(2);
const error = 'get coverage policy info error';
store.issConfig.isCoverageEnabled = true;

View file

@ -7,6 +7,7 @@ $svg-loading-modal-image: "data:image/svg+xml;charset=UTF-8,%3csvg fill='none' x
$svg-modal-header-button-close: "data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.6874 1.82179L9.50889 8L15.6874 14.1782C16.1042 14.595 16.1042 15.2707 15.6874 15.6875C15.4843 15.8907 15.2146 16 14.9328 16C14.6514 16 14.3818 15.8906 14.1787 15.6875L8.00017 9.50934L1.82171 15.6875C1.6182 15.8907 1.34876 16 1.06708 16C0.785733 16 0.516056 15.8906 0.312965 15.6875C-0.10429 15.2706 -0.10429 14.5952 0.312797 14.1784L6.03276 8.45867L6.49146 8L0.312945 1.82177C-0.10429 1.40483 -0.10429 0.729418 0.312797 0.31262C0.728936 -0.104145 1.40489 -0.104051 1.82185 0.31262L7.54152 6.03202L8.00017 6.49065L14.1786 0.312472C14.5955 -0.104107 15.2707 -0.104201 15.6874 0.312452C16.1042 0.729289 16.1042 1.40496 15.6874 1.82179Z' fill='%231574A1'/%3E%3C/svg%3E%0A";
$svg-payment-method-review-toggle: "data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.9' xml:space='preserve'%3e%3cpath d='M8 8.9c-.2 0-.5-.1-.6-.3L.3 1.5C.1 1.4 0 1.1 0 .9 0 .7.1.4.3.3.4.1.7 0 .9 0c.2 0 .5.1.6.3L8 6.7 14.5.2c.1-.1.4-.2.6-.2.2 0 .5.1.6.3s.3.4.3.6c0 .2-.1.5-.3.6L8.6 8.6c-.1.2-.4.3-.6.3z' fill='%231474a2'/%3e%3c/svg%3e";
$svg-search-icon: "data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.7817 14.7328L11.8252 10.7762C12.8833 9.45005 13.3936 7.76911 13.2513 6.07849C13.1091 4.38788 12.325 2.81587 11.0601 1.6852C9.79515 0.554524 8.14538 -0.0490261 6.44946 -0.00154744C4.75353 0.0459312 3.14012 0.740836 1.94045 1.94051C0.740775 3.14018 0.0458701 4.75359 -0.00160848 6.44952C-0.0490871 8.14545 0.554463 9.79521 1.68514 11.0601C2.81581 12.325 4.38782 13.1091 6.07843 13.2514C7.76905 13.3937 9.44999 12.8834 10.7762 11.8252L14.7349 15.7839C14.8044 15.8527 14.8869 15.907 14.9774 15.9439C15.068 15.9808 15.165 15.9995 15.2628 15.9989C15.3606 15.9983 15.4573 15.9784 15.5475 15.9405C15.6376 15.9025 15.7194 15.8471 15.7881 15.7776C15.8568 15.708 15.9112 15.6256 15.9481 15.535C15.985 15.4444 16.0036 15.3474 16.0031 15.2496C16.0025 15.1518 15.9826 15.0551 15.9446 14.965C15.9067 14.8748 15.8513 14.7931 15.7817 14.7243V14.7328ZM6.63737 11.7913C5.61803 11.7913 4.62157 11.4891 3.77402 10.9228C2.92646 10.3564 2.26587 9.5515 1.87578 8.60975C1.4857 7.668 1.38363 6.63172 1.5825 5.63196C1.78136 4.6322 2.27222 3.71386 2.99301 2.99307C3.7138 2.27229 4.63214 1.78142 5.6319 1.58256C6.63166 1.38369 7.66793 1.48576 8.60969 1.87585C9.55144 2.26593 10.3564 2.92652 10.9227 3.77408C11.489 4.62163 11.7913 5.61809 11.7913 6.63743C11.7896 8.00382 11.2461 9.31376 10.2799 10.2799C9.3137 11.2461 8.00376 11.7897 6.63737 11.7913Z' fill='%231574A1'/%3E%3C/svg%3E%0A";
$svg-error-search-icon: "data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.7817 14.7328L11.8252 10.7762C12.8833 9.45005 13.3936 7.76911 13.2513 6.07849C13.1091 4.38788 12.325 2.81587 11.0601 1.6852C9.79515 0.554524 8.14538 -0.0490261 6.44946 -0.00154744C4.75353 0.0459312 3.14012 0.740836 1.94045 1.94051C0.740775 3.14018 0.0458701 4.75359 -0.00160848 6.44952C-0.0490871 8.14545 0.554463 9.79521 1.68514 11.0601C2.81581 12.325 4.38782 13.1091 6.07843 13.2514C7.76905 13.3937 9.44999 12.8834 10.7762 11.8252L14.7349 15.7839C14.8044 15.8527 14.8869 15.907 14.9774 15.9439C15.068 15.9808 15.165 15.9995 15.2628 15.9989C15.3606 15.9983 15.4573 15.9784 15.5475 15.9405C15.6376 15.9025 15.7194 15.8471 15.7881 15.7776C15.8568 15.708 15.9112 15.6256 15.9481 15.535C15.985 15.4444 16.0036 15.3474 16.0031 15.2496C16.0025 15.1518 15.9826 15.0551 15.9446 14.965C15.9067 14.8748 15.8513 14.7931 15.7817 14.7243V14.7328ZM6.63737 11.7913C5.61803 11.7913 4.62157 11.4891 3.77402 10.9228C2.92646 10.3564 2.26587 9.5515 1.87578 8.60975C1.4857 7.668 1.38363 6.63172 1.5825 5.63196C1.78136 4.6322 2.27222 3.71386 2.99301 2.99307C3.7138 2.27229 4.63214 1.78142 5.6319 1.58256C6.63166 1.38369 7.66793 1.48576 8.60969 1.87585C9.55144 2.26593 10.3564 2.92652 10.9227 3.77408C11.489 4.62163 11.7913 5.61809 11.7913 6.63743C11.7896 8.00382 11.2461 9.31376 10.2799 10.2799C9.3137 11.2461 8.00376 11.7897 6.63737 11.7913Z' fill='" + $svg-calendar-error-stroke-color + "'/%3E%3C/svg%3E%0A";
$svg-select-icon: "data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%231474a2'/%3e%3c/svg%3e";
$svg-shop-list-button-green-availability: "data:image/svg+xml,%3Csvg viewBox='0 0 13 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.5 12C3.19159 12 0.5 9.30841 0.5 6C0.5 2.69159 3.19159 0 6.5 0C9.80841 0 12.5 2.69159 12.5 6C12.5 9.30841 9.80841 12 6.5 12ZM6.5 0.785047C3.62449 0.785047 1.28505 3.12449 1.28505 6C1.28505 8.87551 3.62449 11.215 6.5 11.215C9.37551 11.215 11.715 8.87551 11.715 6C11.715 3.12449 9.37551 0.785047 6.5 0.785047Z' fill='%23006A36'/%3E%3Cpath d='M5.697 7.95252C5.5927 7.95252 5.49289 7.91102 5.41999 7.837L3.90597 6.32299C3.75233 6.16934 3.75233 5.92149 3.90597 5.76785C4.05962 5.6142 4.30747 5.6142 4.46111 5.76785L5.69812 7.00373L8.53999 4.16186C8.69364 4.00822 8.94149 4.00822 9.09513 4.16186C9.24878 4.31551 9.24878 4.56336 9.09513 4.717L5.97626 7.83588C5.90224 7.9099 5.80242 7.9514 5.69925 7.9514L5.697 7.95252Z' fill='%23006A36'/%3E%3C/svg%3E%0A";
$svg-shop-list-button-orange-availability: "data:image/svg+xml,%3Csvg viewBox='0 0 13 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.5 0C9.81368 0 12.5 2.68632 12.5 6C12.5 9.31368 9.81368 12 6.5 12C3.18632 12 0.5 9.31368 0.5 6C0.5 2.68632 3.18632 0 6.5 0ZM6.5 0.84C3.6548 0.84 1.34 3.1548 1.34 6C1.34 8.8452 3.6548 11.16 6.5 11.16C9.3452 11.16 11.66 8.8452 11.66 6C11.66 3.1548 9.3452 0.84 6.5 0.84ZM7.90018 4.00596C8.06422 3.84204 8.33002 3.84192 8.49406 4.00596C8.6581 4.17 8.6581 4.43592 8.49406 4.59996L7.0939 6L8.49406 7.40004C8.6581 7.56408 8.6581 7.83 8.49406 7.99404C8.4121 8.076 8.30458 8.11704 8.19706 8.11704C8.08966 8.11704 7.98214 8.076 7.90018 7.99404L6.50002 6.594L5.09986 7.99404C5.01778 8.076 4.91038 8.11704 4.80286 8.11704C4.69546 8.11704 4.58794 8.076 4.50598 7.99404C4.34182 7.83 4.34182 7.56408 4.50598 7.40004L5.90614 6L4.50598 4.59996C4.34182 4.43592 4.34182 4.17 4.50598 4.00596C4.66978 3.84192 4.93582 3.84204 5.09986 4.00596L6.50002 5.406L7.90018 4.00596Z' fill='%23E86421'/%3E%3C/svg%3E%0A";