Merge pull request #820 from Safelite/feature/richardson/SSR-325.2

Continue with Cookie
This commit is contained in:
brich1212safe 2024-08-19 10:37:46 -04:00 committed by GitHub
commit e154f2622c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 361 additions and 28 deletions

View file

@ -10,7 +10,8 @@ const bailoutCode = Object.freeze({
RequestCallback: 8,
HeavyTruckVehicle: 9,
NoPartsAvailable: 10,
PartsServiceError: 11
PartsServiceError: 11,
SafeliteNotTheProvider: 12
});
export default bailoutCode;

View file

@ -60,6 +60,10 @@ const bailoutMessage = Object.freeze({
PartsServiceError: (error) => ({
code: bailoutCode.PartsServiceError,
message: `An error occurred in getPartsOrQuestions. Error: ${getItemData(error)}`
}),
SafeliteNotTheProvider: () => ({
code: bailoutCode.SafeliteNotTheProvider,
message: 'User selected to continue a referral where a TPA shop was previously selected.'
})
});

View file

@ -96,7 +96,7 @@ export function getISSCookie() {
Used to set properties on the ISS cookie.
Takes an object with properties to set. Will overwrite existing properties.
*/
function setISSCookieProperties(properties) {
export function setISSCookieProperties(properties) {
if (typeof properties === 'object') {
const cookie = getISSCookie();
@ -114,20 +114,24 @@ function setISSCookieProperties(properties) {
/*
Will update the cookie if present, or create a new one if not.
*/
export function updateOrCreateISSCookie() {
export function updateOrCreateISSCookie(forceCreate = false) {
const store = useMainStore();
// Set up cookie with all the props.
setISSCookieProperties({
LastTouched: new Date().toUTCString(),
SavedSessionTimeoutDate: store.applicationUser.savedSessionTimeout,
ShouldResetState: false,
ReferralNumber: store.order.referralNumber,
ReferralDate: store.order.referralDate,
ReferralCorrelationId: store.order.referralCorrelationId,
ReferralParentAccountNumber: store.order.parentAccountNumber,
SavedSessionId: store.applicationUser.savedSessionId
});
if (forceCreate || store.order?.referralDate) {
setISSCookieProperties({
LastTouched: new Date().toUTCString(),
SavedSessionTimeoutDate: store.applicationUser.savedSessionTimeout,
ShouldResetState: false,
ReferralNumber: store.order.referralNumber,
ReferralDate: store.order.referralDate,
ReferralCorrelationId: store.order.referralCorrelationId,
ReferralParentAccountNumber: store.order.parentAccountNumber,
SavedSessionId: store.applicationUser.savedSessionId,
VehicleMake: store.order.vehicle?.make,
VehicleModel: store.order.vehicle?.model
});
}
}
/*

View file

@ -78,3 +78,12 @@ export function deepEqual(obj1, obj2) {
return obj1 === obj2;
}
export function getPropertyCaseInsensitive(obj, property) {
const props = [];
for (const i in obj) if (Object.prototype.hasOwnProperty.call(obj, i)) props.push(i);
let prop;
// eslint-disable-next-line no-cond-assign
while (prop = props.pop()) if (prop.toLowerCase() === property.toLowerCase()) return prop;
return null;
}

View file

@ -1,5 +1,4 @@
import { useMainStore } from '@/store';
import { updateOrCreateISSCookie } from '@/helpers/cookie-helper';
import submitType from '@/constants/submit-type';
/*
@ -10,7 +9,6 @@ async function saveSessionHelper(store, { submitAfterSave }) {
if (savedSessionInfo) {
store.setSaveSessionInfo(savedSessionInfo.data);
}
updateOrCreateISSCookie();
}
/*

View file

@ -0,0 +1,94 @@
<template>
<transition
name="fade"
mode="out-in">
<div class="continue-modal-container">
<modal
ref="continueModal"
:headerText="modalHeaderText"
:footerButtonText="modalFooterText"
@isModalOpened="setModalStatus"
@footerButtonEvent="startNewReferral">
<p class="mb-2 subheader-text text-center">
{{ modalSubHeaderText }}
</p>
<button
id="btnContinueReferral"
type="button"
class="btn btn-white-blue continue-referral align-items-center justify-content-center mt-2 py-3 px-4 delay w-100"
@click="continueReferral">
{{ modalBodyText }}
</button>
</modal>
</div>
</transition>
</template>
<script>
import modal from '@/digital-components/modal/modal.vue';
import { getISSCookie } from '@/helpers/cookie-helper.js';
export default {
name: 'continue-modal',
components: {
modal
},
emits: ['continue-previous-referral', 'start-new-referral'],
data() {
return {
isModalOpened: false
};
},
computed: {
getMakeModelString() {
const cookie = getISSCookie();
return `${cookie.VehicleMake} ${cookie.VehicleModel}`;
},
modalBodyText() {
return this.getCmsContent('ContinueReferralModalWidget', 'BodyText');
},
modalHeaderText() {
return this.getCmsContent('ContinueReferralModalWidget', 'HeaderText');
},
modalFooterText() {
return this.getCmsContent('ContinueReferralModalWidget', 'FooterText');
},
modalSubHeaderText() {
return this.getCmsContent('ContinueReferralModalWidget', 'SubheaderText')
.replaceAll('{custom:mmFound}', this.getMakeModelString);
}
},
methods: {
openModal() {
this.$refs.continueModal.openModal();
},
setModalStatus(isOpened) {
this.isModalOpened = isOpened;
},
closeModal() {
this.$refs.continueModal.closeModal();
},
continueReferral() {
this.$emit('continue-previous-referral');
},
startNewReferral() {
this.$emit('start-new-referral');
}
}
};
</script>
<style lang="scss" scoped>
.btn-white-blue {
border-radius: 0.5rem;
border-color: $blue;
color: $blue;
font-size: 1rem;
font-weight: $font-weight-bold;
line-height: 1.5rem;
background-color: #ffffff;
--bs-btn-focus-shadow-rgb: 218, 72, 62;
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}
</style>

View file

@ -10,6 +10,7 @@
// Supporting files
import issPageValues from '@/router/router-constants/issPage-values';
import { validateISSClientTag, validateISSClientSignature } from '@/helpers/clientauth-helper';
import { getISSCookie, updateOrCreateISSCookie } from '@/helpers/cookie-helper.js';
import { useMainStore } from '@/store';
import applicationConfig from '@/constants/application-config';
@ -41,6 +42,28 @@ export default {
if (isAuthorized) {
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);
}
} catch {
updateOrCreateISSCookie(true);
}
if (clientData.parameters?.length > 0) {
const finalParams = this.combineClientParameters(clientData.parameters, queryStringParams);
this.populateStoreItemsFromParams(finalParams);

View file

@ -120,6 +120,11 @@
isRequired
isSmallQuestionLabelText
disableAutoFill />
<continueModal
ref="continueModal"
modalWidgetName="ContinueModalWidget"
@continuePreviousReferral="loadReferralFromCookie"
@startNewReferral="startNewReferral" />
<div
id="welcomeFooter"
class="row">
@ -153,6 +158,7 @@ import siteHeader from '@/iss-components/site-header/site-header.vue';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import alert from '@/ux-components/alert/alert.vue';
import continueModal from '@/iss-components/continue-modal/continue-modal.vue';
import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue';
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
import dropdownQuestion from '@/digital-components/dropdown-question/dropdown-question.vue';
@ -174,7 +180,9 @@ import states from '@/constants/states';
import globalRules from '@/constants/global-rules';
import routerParams from '@/router/router-constants/router-params';
import MaskaFormattedMasks from '@/constants/maska-masks';
import { getPropertyCaseInsensitive } from '@/helpers/object-helper';
import { saveSession } from '@/helpers/order-helper';
import { getISSCookie } from '@/helpers/cookie-helper.js';
import bailoutMessage from '@/constants/bailoutMessage';
// define validation rules
@ -191,6 +199,7 @@ export default {
footerImage,
alert,
buttonQuestion,
continueModal,
textboxQuestion,
dropdownQuestion,
siteFooter,
@ -222,6 +231,7 @@ export default {
next((vm) => {
updateCmsSiteHeader(resultMap.globalSiteHeaderCmsContent);
vm.setCmsContent(resultMap.cmsContent);
vm.checkContinueFromCookie();
});
},
setup() {
@ -235,6 +245,7 @@ export default {
data() {
return {
welcomePageModel: this.getWelcomePageModelFromStore(),
answeredContinueModal: false,
displayInvalidZipAlert: false,
duplicates: [],
rules: {
@ -302,11 +313,13 @@ export default {
async forwardButtonAction() {
try {
this.mainStore.updatePolicyData(this.welcomePageModel);
await Promise.allSettled([
this.configureZip().then(async () => await this.mainStore.getBillToInfo()),
this.mainStore.getDuplicateReferrals(),
this.mainStore.getCoveragePolicyInfo()
]);
const promises = [];
promises.push(this.configureZip().then(async () => await this.mainStore.getBillToInfo()));
if (!this.mainStore.order.loadedFromCookie) {
promises.push(this.mainStore.getDuplicateReferrals());
}
promises.push(this.mainStore.getCoveragePolicyInfo());
await Promise.allSettled(promises);
} catch (e) {
console.error(e);
// TODO: Bailout?
@ -339,7 +352,8 @@ export default {
{},
{ [routerParams.SKIP_SAVE_SESSION]: true }
);
} else if (this.mainStore.applicationUser.duplicateOrders?.length > 0 ?? false) {
} else if ((this.mainStore.applicationUser.duplicateOrders?.length > 0 ?? false)
&& !this.answeredContinueModal) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_DUPLICATES,
this.$route,
@ -389,6 +403,71 @@ export default {
email: this.mainStore.order.customer.emailAddress,
isPolicyNumberDisabled: this.mainStore.order.policy.isPolicyNumberDisabled
};
},
updateWelcomePageModel(response) {
const getDamageCause = this.findDamageCause(response.policy.damageCause);
this.mainStore.order.policy.damageCause = getDamageCause;
this.welcomePageModel.policyNumber = response.policy.policyNumber;
this.welcomePageModel.policyZipCode = response.policy.policyZipCode;
this.welcomePageModel.dateOfLoss = response.policy.dateOfLoss;
this.welcomePageModel.damageCause = getDamageCause;
this.welcomePageModel.damageState = response.policy.damageState;
this.welcomePageModel.damageCity = response.policy.damageCity;
this.welcomePageModel.isDamageGlassOnly = response.policy.isDamageGlassOnly;
this.welcomePageModel.phoneNumber = response.customer.homePhone;
this.welcomePageModel.email = response.customer.emailAddress;
},
findDamageCause(damageCause) {
const damageCauseOptions = this.DamageCauseOptions;
return getPropertyCaseInsensitive(damageCauseOptions, damageCause);
},
checkContinueFromCookie() {
if (this.mainStore.issConfig.enableContinueFromCookie) {
this.showCookieDrawer();
}
},
showCookieDrawer() {
this.$refs.continueModal.openModal();
},
async loadReferralFromCookie() {
const issCookie = getISSCookie();
if (issCookie) {
const cookieReferral = {
referralNumber: issCookie.ReferralNumber,
responseDate: issCookie.ReferralDate,
parentAccountNumber: issCookie.ReferralParentAccountNumber,
correlationId: issCookie.ReferralCorrelationId
};
try {
await this.mainStore.loadSessionFromCookie(cookieReferral)
.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
);
} else {
this.updateWelcomePageModel(response);
}
});
} catch (error) {
// eslint-disable-next-line no-console
console.error(`Error on loading session from cookie ${error}`);
this.startNewReferral();
} finally {
this.$refs.continueModal.closeModal();
}
}
},
startNewReferral() {
this.mainStore.issConfig.enableContinueFromCookie = false;
this.answeredContinueModal = true;
this.$refs.continueModal.closeModal();
}
}
};

View file

@ -17,9 +17,8 @@ import analyticsMixin from '@/mixins/analytics-mixin';
import { saveSession } from '@/helpers/order-helper.js';
import routerParams from '@/router/router-constants/router-params';
import canBailoutNavigateBack from '@/helpers/bailout-helper';
import bailoutMessage from '@/constants/bailoutMessage';
import navigationScenarios from './router-constants/navigation-scenarios';
import submitType from '@/constants/submit-type';
import navigationScenarios from './router-constants/navigation-scenarios';
const routes = [
{
@ -28,6 +27,7 @@ const routes = [
async beforeEnter(to, from, next) {
try {
const issPageToUse = !to.query.issPage ? issPageValues.WELCOME_PAGE : to.query.issPage;
const fromQueryPage = from.query?.issPage;
if ((issPageToUse === issPageValues.ACCESS_DENIED
|| (issPageToUse !== issPageValues.ENTRY_PAGE && !useMainStore().issConfig.parentAccountNumber))
@ -61,7 +61,11 @@ const routes = [
}
// Process ISS cookie.
updateOrCreateISSCookie();
// 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.

View file

@ -404,6 +404,10 @@ 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

View file

@ -12,7 +12,6 @@ import issPageValues from '@/router/router-constants/issPage-values';
import damageLocationsSelected from '@/constants/damage-locations-selected';
import coverageStatuses from '@/constants/coverage-statuses';
import { AppointmentTypeStrings } from '@/constants/schedule-constants';
import { deepClone } from '@/helpers/object-helper';
import { convertDateStringToDate, getDateDifferenceInDays, militaryToTwelveHourTime } from '@/helpers/date-helper';
import { paymentMethods } from '@/constants/payment-method-constants';
import webStorageConstants from '@/constants/web-storage-constants';
@ -29,7 +28,7 @@ import { findLineItemIndex, getLineItemsFlattened } from '@/helpers/line-items-h
import { buildQueryStringParameterFromArrayOfComplexObjects, getLineItemQueryString, getTaxLineItemQueryString } from '@/helpers/querystring-helper';
import coverageType from '@/constants/coverage-type';
import { getExperimentSettingValue, getFeatureTogglesQueryString } from '@/helpers/experiment-helper';
import { getSessionKeyValue, getUserIdValue } from '@/helpers/cookie-helper';
import { getSessionKeyValue, getUserIdValue, deleteISSCookie } from '@/helpers/cookie-helper';
const storeId = 'main';
@ -207,11 +206,13 @@ export const getDefaultState = () => ({
referralCorrelationId: '00000000-0000-0000-0000-000000000000',
referralSequenceNumber: null,
eon: null,
workOrderId: null,
workOrderNumber: null,
customerPortalLoginToken: null,
originalDeductible: null,
currentDeductible: null,
carrierPhoneNumber: null,
loadedFromCookie: null,
loadedFromDupeCheck: null,
loadedSessionClearedPreviousData: null,
availableVaps: null
@ -240,6 +241,7 @@ export const getDefaultState = () => ({
isCoverageEnabled: false, // Indicates if we should be calling coverage on this flow.
isClaimRegistrationRequired: false, // Indicates if the claim registration call needs to be made for a client to complete coverage verification.
isAuthenticated: false, // Indicates if user is authenticated or not.
enableContinueFromCookie: false, // Indicates if the continue from cookie modal should appear.
enableTPAFlow: false, // Indicates if the TPA flow is supported for this client.
successReturnURL: null, // Client URL to return the user to upon a successfull flow completetion (order submission).
failureReturnURL: null, // Client URL to return the user to when they encounter an error or bailout and are unable to complete the flow.
@ -1369,7 +1371,29 @@ export const useMainStore = defineStore({
}, (error) => reject(error));
});
},
/**
* @function getCookieValueByName
* @param {object} payload
* referralNumber: the sv2 referral's referralNumber
* responseDate: the sv2 referral's referralDate or the responseDate associated with the returned duplicate-check record
* parentAccountNumber: the client's parentAccountNumber associated with the sv2 referral
* correlationId: the correlationId associated with the sv2 referral
* @summary
* Gets cookie value by name, returns empty string if not found.
* @returns {object} returns the response object for the load session api, which returns the sv2 referral information
*/
async getReferralByPayload(payload) {
return await globalMethods.callHttpClient({
method: endpoints.LoadSession.method,
endpoint: endpoints.LoadSession.url,
payload: {
referralNumber: payload.referralNumber,
referralDate: payload.responseDate,
parentAccountNumber: payload.parentAccountNumber,
referralCorrelationId: payload.correlationId
}
});
},
async loadSession(duplicate) {
const { order, issConfig } = this;
const response = await globalMethods.callHttpClient({
@ -1398,7 +1422,7 @@ export const useMainStore = defineStore({
order.contactInfo.lastName = data?.customer?.lastName;
order.contactInfo.emailAddress = data?.customer?.emailAddress;
order.contactInfo.homePhone = data?.customer?.homePhone;
order.contactInfo.servicephone = data?.customer?.servicePhone;
order.contactInfo.servicePhone = data?.customer?.servicePhone;
order.contactInfo.alternativePhone = data?.customer?.alternativePhone;
order.contactInfo.requestTextUpdates = data?.customer?.isSmsOptIn;
@ -1450,6 +1474,90 @@ export const useMainStore = defineStore({
order.loadedFromDupeCheck = true;
order.loadedSessionClearedPreviousData = false;
},
async loadSessionFromCookie(cookie) {
const { order, issConfig } = this;
const response = await this.getReferralByPayload(cookie);
const { data } = response;
if (data) {
order.customer.address.streetAddress = data.customer?.address?.streetAddress;
order.customer.address.streetAddress2 = data.customer?.address?.streetAddress2;
order.customer.address.city = data.customer?.address?.city;
order.customer.address.state = data.customer?.address?.state;
order.customer.address.zipCode = data.customer?.address?.zipCode;
order.customer.emailAddress = data.customer?.emailAddress;
order.contactInfo.firstName = data?.customer?.firstName;
order.contactInfo.lastName = data?.customer?.lastName;
order.contactInfo.emailAddress = data?.customer?.emailAddress;
order.contactInfo.homePhone = data?.customer?.homePhone;
order.contactInfo.servicePhone = data?.customer?.servicePhone;
order.contactInfo.alternativePhone = data?.customer?.alternativePhone;
order.contactInfo.requestTextUpdates = data?.customer?.isSmsOptIn;
if (data.insuranceCoverage) {
order.insuranceCoverage.coverageType = data?.insuranceCoverage?.coverageType;
order.insuranceCoverage.coverageStatus = data?.insuranceCoverage?.coverageStatus;
order.insuranceCoverage.claimNumber = data?.insuranceCoverage?.claimNumber;
} else if (data?.payment?.insuranceCoverage) {
if (data?.payment?.insuranceCoverage?.isVerified) {
order.insuranceCoverage.coverageStatus = coverageStatuses.VERIFIED;
}
order.insuranceCoverage.claimNumber = data?.payment?.insuranceCoverage?.claimNumber;
} else {
order.customer.firstName = data?.customer?.firstName;
order.customer.lastName = data?.customer?.lastName;
}
if (order.policy.vehicles.length !== 0 && data.vehicle?.carId) {
order.vehicle.registration.licensePlate = data?.vehicle?.licensePlateNumber;
if (data.vehicle.vin) {
const vehicle = order.policy.vehicles.find((v) => v.vin === data.vehicle.vin);
if (vehicle) {
const vehicleResponse = await this.lookupVehicleByVin(vehicle.vin);
if (vehicleResponse) {
this.updateVehicle({
...vehicleResponse.data,
policyVehicleId: vehicle.id,
vin: vehicle.vin
});
this.updateVehicleCoverage({
noCoverage: noCoverageForSelectedVehicle(vehicle),
deductible: deductibleForSelectedVehicle(vehicle),
repairWaived: repairWaivedForSelectedVehicle(vehicle),
endorsements: endorsementsForSelectedVehicle(vehicle)
});
}
}
}
order.vehicle.year = data.vehicle.year;
order.vehicle.make = data.vehicle.make;
order.vehicle.model = data.vehicle.model;
order.vehicle.style = data.vehicle.style;
}
order.policy.damageCause = data?.policy?.damageCause;
order.policy.damageCity = data?.policy?.damageCity;
order.policy.damageState = data?.policy?.damageState;
order.policy.dateOfLoss = data?.policy?.dateOfLoss;
order.policy.isDamageGlassOnly = data?.policy?.isDamageGlassOnly;
order.policy.policyNumber = data?.policy?.policyNumber;
order.policy.policyZipCode = data?.policy?.policyZipCode;
order.referralNumber = data?.referralNumber;
order.referralDate = data?.referralDate;
order.referralCorrelationId = data?.referralCorrelationId;
order.referralSequenceNumber = data?.referralSequenceNumber;
order.eon = data?.eon;
order.workOrderId = data?.workOrderId;
order.loadedFromCookie = true;
order.loadedSessionClearedPreviousData = false;
issConfig.enableContinueFromCookie = false;
this.updateIsSafeliteProvider(data?.provider?.isSafeliteProvider);
}
return data;
},
updateCreditCardToken(token) {
this.order.payment.creditCardToken.subscriptionId = token.subscriptionId;
this.order.payment.creditCardToken.expMonth = token.expMonth;
@ -1746,6 +1854,7 @@ export const useMainStore = defineStore({
this.order.referralSequenceNumber = null;
this.order.referralDate = null;
this.order.referralSequenceNumber = null;
this.order.workOrderId = null;
this.order.workOrderNumber = null;
this.order.eon = null;
this.order.originalDeductible = null;
@ -1855,6 +1964,7 @@ export const useMainStore = defineStore({
this.issConfig.styleSheet = '';
this.issConfig.isCoverageEnabled = false;
this.issConfig.isAuthenticated = false;
this.issConfig.enableContinueFromCookie = false;
this.issConfig.enableTPAFlow = false;
this.issConfig.isClaimRegistrationRequired = false;
this.issConfig.successReturnURL = null;
@ -2559,6 +2669,9 @@ export const useMainStore = defineStore({
// clear vuex
this.resetState();
// delete cookie
deleteISSCookie();
// restore issConfig
this.issConfig = issConfig;
// restore user's experiments