merging with develop

This commit is contained in:
Michaela Brydon 2024-08-07 16:25:18 -04:00
commit ca97fbd658
20 changed files with 558 additions and 312 deletions

View file

@ -39,6 +39,23 @@ export default {
}
}
};
// Add a global Javascript exception handler for logging exceptions, this handler will log when there is an uncaught exception
window.onerror = (msg, url, line, col, error) => {
// Log the windows error
// Note that col & error are new to the HTML 5 spec and may not be
// supported in every browser. It worked for me in Chrome.
global.$logger.logError(msg, {
url: url,
line: line,
column: col ?? "",
error: error ?? "",
});
// If you return true, then error alerts (like in older versions of
// Internet Explorer) will be suppressed.
var suppressErrorAlert = true;
return suppressErrorAlert;
};
</script>
<style lang="scss">

View file

@ -22,7 +22,8 @@ const applicationConfig = Object.freeze({
GOOGLE_CALENDAR: 'https://www.google.com/calendar/render?action=TEMPLATE',
YAHOO_CALENDAR: 'https://calendar.yahoo.com/?v=60',
OUTLOOK_CALENDAR:
'https://outlook.office.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent'
'https://outlook.office.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent',
FRONTEND_LOGGER_PATH: "/analytics/api/v1/logging"
});
export default applicationConfig;

View file

@ -0,0 +1,11 @@
const axiosResponseInterceptorMessages = {
NETWORK_ERROR:
"A network error occurred. " +
"This could be a bad URL, a CORS issue, or a dropped internet connection. " +
"It is impossible for us to know.",
STATUS_CODE_ERROR: "Status Code Error",
NO_RESPONSE_ERROR: "The request was made but no response was received",
GENERIC_ERROR: "Error",
};
export default axiosResponseInterceptorMessages;

View file

@ -141,11 +141,11 @@ const endpoints = Object.freeze({
method: 'POST'
},
LookupVinByAddress: {
url: `${VEHICLE_BASE_URL}/lookup-by-address`,
url: `${VEHICLE_BASE_URL}/lookup-vin-by-address`,
method: 'POST'
},
LookupVinByPlate: {
url: `${VEHICLE_BASE_URL}/lookup-by-plate`,
url: `${VEHICLE_BASE_URL}/lookup-vin-by-plate`,
method: 'POST'
},
InitializeSession: {

View file

@ -0,0 +1,8 @@
const loggingEndpointMethods = {
LOG_INFORMATION: "log-information",
LOG_WARNING: "log-warning",
LOG_ERROR: "log-error",
LOG_CRITICAL: "log-critical",
};
export default loggingEndpointMethods;

View file

@ -1,6 +1,7 @@
const submitType = Object.freeze({
SAFELITE: 0,
TPA: 1
TPA: 1,
BAILOUT: 2
});
export default submitType;

View file

@ -5,6 +5,50 @@ import { useMainStore } from '@/store';
import applicationConfig from '@/constants/application-config.js';
import { GaCategories, GaActions, GaLabels } from '@/constants/analytics';
import headerKeys from '@/constants/header-keys';
import axiosResponseInterceptorMessages from "@/constants/axios-response-interceptor-messages.js";
// Add a response interceptor for global axios error handing.
axios.interceptors.response.use(
(response) => response,
(error) => {
let rejectionError = "";
if (typeof error.response === "undefined") {
// The request was not made, could be a bad url, bad connection or a CORS error.
rejectionError = {
message:
"A network error occurred. " +
"This could be a CORS issue or a dropped internet connection. " +
"It is impossible for us to know.",
cause: error,
response: error,
message: axiosResponseInterceptorMessages.NETWORK_ERROR
};
} else if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
rejectionError = {
response: error.response,
message: axiosResponseInterceptorMessages.STATUS_CODE_ERROR,
};
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
rejectionError = {
response: error.request,
message: axiosResponseInterceptorMessages.NO_RESPONSE_ERROR,
};
} else {
// Something happened in setting up the request that triggered an Error
rejectionError = {
response: error.message,
message: axiosResponseInterceptorMessages.GENERIC_ERROR,
};
}
return Promise.reject(rejectionError);
}
);
export default {
callHttpClient({ method, endpoint, payload, logApiCall = true }) {
@ -37,13 +81,23 @@ export default {
return resolve(response);
},
(error) => {
window.console.error(error);
// implement if analytics service is down
if (endpoint.includes('analytics')) {
return resolve({ data: '' });
if (logApiCall) {
analyticsMixIn.methods.pushEventToGA(
GaCategories.API_RESPONSE,
GaActions.RESULT,
`${GaLabels.ERROR}_${endpoint}`,
true
);
}
if (error.response.status != "404") {
global.$logger.logError(
`${method}: ${endpoint}: ${error.message}`,
error.response
);
}
return reject(error.response);
}
);

View file

@ -7,6 +7,13 @@ import { getMountOptions } from '@/helpers/unit-test-helper.js';
jest.mock('axios');
jest.mock('@/mixins/analytics-mixin');
global.$logger = {
logInformation: jest.fn(),
logWarning: jest.fn(),
logError: jest.fn(),
logCritical: jest.fn(),
};
/** @ignore */
function setupMocksForHttpClient({
endpoint = null,

93
src/helpers/logger.js Normal file
View file

@ -0,0 +1,93 @@
import applicationConfig from "@/constants/application-config.js";
import axios from "axios";
import { useMainStore } from "@/store";
import loggingEndpointMethods from "@/constants/logging-endpoint-methods";
import headerKeys from "@/constants/header-keys";
export class Logger {
logInformation(message, details) {
this.writeLogEntry(
loggingEndpointMethods.LOG_INFORMATION,
this.formatLogEntry(message, details)
);
}
logWarning(message, details) {
this.writeLogEntry(
loggingEndpointMethods.LOG_WARNING,
this.formatLogEntry(message, details)
);
}
logError(message, details) {
this.writeLogEntry(loggingEndpointMethods.LOG_ERROR, this.formatLogEntry(message, details));
}
logCritical(message, details) {
this.writeLogEntry(
loggingEndpointMethods.LOG_CRITICAL,
this.formatLogEntry(message, details)
);
}
formatLogEntry(message, details) {
return `Application: ${applicationConfig.APPLICATION_NAME}\n${message}\n${
details ? JSON.stringify(details, undefined, 2) : ""
}`;
}
writeLogEntry(endpoint, logEntry) {
const store = useMainStore();
return new Promise((resolve, reject) => {
// If running locally or in the Dev environment show the log entries in the console.
if (
applicationConfig.CURRENT_ENVIRONMENT === "Localhost" ||
applicationConfig.CURRENT_ENVIRONMENT === "Dev" ||
applicationConfig.CURRENT_ENVIRONMENT === "SysTest"
) {
switch (endpoint) {
case loggingEndpointMethods.LOG_INFORMATION:
console.info(logEntry);
break;
case loggingEndpointMethods.LOG_WARNING:
console.warn(logEntry);
break;
case loggingEndpointMethods.LOG_ERROR:
console.error(logEntry);
break;
case loggingEndpointMethods.LOG_CRITICAL:
console.error(logEntry);
break;
default:
console.log(logEntry);
}
}
const url =
applicationConfig.CONSUMER_CF_DISTRO + applicationConfig.FRONTEND_LOGGER_PATH;
const headers = {
[headerKeys.EXPERIMENT]: JSON.stringify(store.experimentSettings),
};
axios({
method: "POST",
url: `${url}/${endpoint}`,
data: { entry: logEntry },
crossDomain: true,
responseType: {},
headers: headers,
}).then(
(response) => {
return resolve(response);
},
(error) => {
return reject(error);
}
);
});
}
}
export default Logger;

View file

@ -1,5 +1,6 @@
import { useMainStore } from '@/store';
import { updateOrCreateISSCookie } from '@/helpers/cookie-helper';
import submitType from '@/constants/submit-type';
/*
Encapsulates asynchronous Save Session logic inside a promise to allow for Save Session queuing
@ -43,3 +44,10 @@ export async function submitWorkOrder({ submitType }) {
});
store.createSubmittedOrder(submitType);
}
export async function submitBailout() {
const store = useMainStore();
store.resetSubmittedOrder();
await store.saveSession({});
store.createSubmittedOrder(submitType.BAILOUT);
}

View file

@ -91,7 +91,7 @@ import canBailoutNavigateBack from '@/helpers/bailout-helper';
import BailoutCode from '@/constants/bailoutCode';
import issPageValues from '@/router/router-constants/issPage-values';
import MaskaFormattedMasks from '@/constants/maska-masks';
import { saveSession } from '@/helpers/order-helper';
import { submitBailout } from '@/helpers/order-helper';
export default {
name: 'bailout-page',
@ -203,7 +203,7 @@ export default {
},
async forwardButtonAction() {
this.mainStore.setBailoutContactInfo(this.bailoutPageModel);
await saveSession({ shouldAwaitSaveSessionQueue: true });
await submitBailout();
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route,

View file

@ -297,7 +297,7 @@ export default {
this.$refs.siteFooter.updateButtonText(newValue);
},
async forwardButtonAction() {
await useMainStore().savePaymentMethodChoice(this.paymentMethod);
useMainStore().savePaymentMethodChoice(this.paymentMethod);
if (this.paymentMethod === paymentMethods.PAY_AT_TIME_OF_SERVICE) {
try {

View file

@ -50,7 +50,7 @@ export default {
data() {
return {
servicePackageRadio,
selectedPackageName: packageNames.TIER_ONE
selectedPackageName: ''
};
},
computed: {
@ -64,10 +64,6 @@ export default {
servicePackageAnswers() {
if (!this.cmsWidgetName) return [];
const cmsAnswersContent = [];
cmsAnswersContent.push({
Name: 'TierOne',
cmsWidgetName: 'EconomyServicePackage'
});
if (this.shouldDisplayTierTwoPackage) {
cmsAnswersContent.push({
Name: 'TierTwo',
@ -78,6 +74,10 @@ export default {
Name: 'TierThree',
cmsWidgetName: 'PremiumServicePackage'
});
cmsAnswersContent.push({
Name: 'TierOne',
cmsWidgetName: 'EconomyServicePackage'
});
// if cms content has not yet loaded, skip
if (!this.getCmsContent(cmsAnswersContent[0].cmsWidgetName, 'HeaderText')
|| this.getCmsContent(cmsAnswersContent[0].cmsWidgetName, 'HeaderText') === '') {

View file

@ -7,17 +7,20 @@
:class="[buttonLabelSubCopy ? 'has-subheader' : '']"
for="testradio">
<div class="package-specs">
<p class="m-0">
<span v-html="buttonLabel"></span>
<span
class="pricing-info"
v-html="buttonAuxiliaryCopy"></span>
</p>
<p
v-if="buttonLabelSubCopy"
class="sub-label m-0"
v-html="buttonLabelSubCopy"></p>
<div>
<p class="m-0">
<span v-html="buttonLabel"></span>
<span
class="pricing-info"
v-html="buttonAuxiliaryCopy"></span>
</p>
<p
v-if="buttonLabelSubCopy"
class="sub-label m-0"
v-html="buttonLabelSubCopy">
</p>
</div>
<div class="hide-when-closed">
<ul>
<li
v-for="listItem in arrayOfListItemsFromBodyText"
@ -139,9 +142,13 @@ export default {
border-radius: 0.5rem;
overflow: hidden;
min-height: 60px;
max-height: 100px;
@include media-breakpoint-up(md) {
max-height: 500px;
}
&.has-subheader {
min-height: 80px;
min-height: 86px;
}
&:before {
@ -222,19 +229,21 @@ export default {
.package-footer {
color: $red;
font-weight: $font-weight-bold;
margin-top: 0.5rem;
font-weight: $font-weight-bold; // 600 in fmg
margin-top: 0.5rem; // not in fmg
}
.package-specs {
display: flex;
flex-direction: column;
width: 100%;
max-height: 1000px;
transition: all 0.5s ease;
max-height: 0;
transition: all 1s ease;
@include media-breakpoint-up(md) {
max-height: 500px;
}
p {
font-weight: 500;
font-weight: $font-weight-bold; // 600 in fmg
display: flex;
justify-content: space-between;
@ -253,16 +262,16 @@ export default {
}
ul {
margin: 1rem 0 0 -.6rem;
margin: 1rem 0 0 -.6rem; // .9375rem in fmg
padding: 0;
li {
margin-bottom: 0.5rem;
font-size: 0.875rem;
line-height: 1.714;
line-height: 1.5rem;
a {
line-height: 1.714;
line-height: 1.5rem;
padding: 0;
}
}
@ -278,6 +287,14 @@ export default {
opacity: 0;
}
}
.hide-when-closed {
display: none;
@include media-breakpoint-up(md) {
display: flex;
flex-direction: column;
}
}
}
}
}

View file

@ -361,7 +361,7 @@ export default {
this.navigate(
this.navigationScenarios.SAVE_SESSION_FAILED,
this.$route,
{ query: { issPage: this.issPageValues.TPA_SUBMIT } }
{ issPage: this.issPageValues.TPA_SUBMIT }
);
});
} catch (error) {

View file

@ -13,6 +13,10 @@ import defineGlobalRules from '@/helpers/global-rule-definer';
import router from './router';
import App from './App.vue';
import Logger from "@/helpers/logger";
// Instantiate global logging object
global.$logger = new Logger();
// Vue App Setup
const vueApp = createApp(App);
@ -35,6 +39,17 @@ vueApp.mixin(baseMixin);
vueApp.mixin(analyticsMixin);
vueApp.mixin(experimentMixin);
// Vue Error Handling
vueApp.config.errorHandler = (err, vm, info) => {
global.$logger.logError(
`Page Name - ${vm.getPageNameByQueryString()} - ${info}: ${err.message}\n${err.stack}`
);
};
// Vue Router Error Handling
router.onError((err) => {
global.$logger.logError(err.message, err.cause);
});
vueApp.mount('#app');
// define global rules

View file

@ -377,6 +377,8 @@ function getConfirmationPageFromOrder(order) {
return issPageValues.ORDER_CONFIRMATION;
case submitType.TPA:
return issPageValues.TPA_CONFIRMATION;
case submitType.BAILOUT:
return issPageValues.CONTACT_CONFIRMATION;
default:
return issPageValues.ORDER_CONFIRMATION;
}

View file

@ -1306,12 +1306,7 @@ export const useMainStore = defineStore({
isCreditCard: payment.payInAdvanceType === paymentMethods.CREDIT_CARD,
isAfterpay: payment.payInAdvanceType === paymentMethods.AFTERPAY,
nextGenSettledAmount: payment.nextGenSettledAmount,
CCToken: payment.creditCardToken,
insuranceCoverage: {
isVerified: this.isVerified,
coverageStatus: this.isNoComp ? coverageStatuses.NO_COVERAGE : this.isVerified ? coverageStatuses.VERIFIED : coverageStatuses.PENDING,
claimNumber: insuranceCoverage.claimNumber
}
CCToken: payment.creditCardToken
},
serviceLocation: {
address: {
@ -1374,90 +1369,83 @@ export const useMainStore = defineStore({
async loadSession(duplicate) {
const { order, issConfig } = this;
try {
const response = await globalMethods.callHttpClient({
method: endpoints.LoadSession.method,
endpoint: endpoints.LoadSession.url,
payload: {
referralNumber: duplicate.referralNumber,
referralDate: duplicate.responseDate,
parentAccountNumber: issConfig.parentAccountNumber,
referralCorrelationId: duplicate.correlationId
const response = await globalMethods.callHttpClient({
method: endpoints.LoadSession.method,
endpoint: endpoints.LoadSession.url,
payload: {
referralNumber: duplicate.referralNumber,
referralDate: duplicate.responseDate,
parentAccountNumber: issConfig.parentAccountNumber,
referralCorrelationId: duplicate.correlationId
}
});
const { data } = response;
if (!data) {
return;
}
if (this.isPolicyLookupSuccessful) {
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.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;
}
});
const { data } = response;
if (!data) {
// TODO how should we handle this case?
return response;
order.insuranceCoverage.claimNumber = data?.payment?.insuranceCoverage?.claimNumber;
}
if (this.isPolicyLookupSuccessful) {
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.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;
}
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)
});
}
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.referralNumber = data?.referralNumber;
order.referralDate = data?.referralDate;
order.referralCorrelationId = data?.referralCorrelationId;
order.referralSequenceNumber = data?.referralSequenceNumber;
order.eon = data?.eon;
order.loadedFromDupeCheck = true;
order.loadedSessionClearedPreviousData = false;
return data;
} catch (ex) {
// TODO how should we handle this case?
throw ex;
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.referralNumber = data?.referralNumber;
order.referralDate = data?.referralDate;
order.referralCorrelationId = data?.referralCorrelationId;
order.referralSequenceNumber = data?.referralSequenceNumber;
order.eon = data?.eon;
order.loadedFromDupeCheck = true;
order.loadedSessionClearedPreviousData = false;
},
updateCreditCardToken(token) {
this.order.payment.creditCardToken.subscriptionId = token.subscriptionId;

View file

@ -1107,21 +1107,6 @@ describe('Store', () => {
endpoint: endpoints.LoadSession.url
}));
});
it('Returns expected response object', async () => {
// Arrange
const response = { data: { ReferralNumber: getRandomString(6, 6) } };
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(response));
const duplicate = {
referralNumber: getRandomString(6, 6),
referralCorrelationId: getRandomGuid()
};
// Act
const result = store.loadSession(duplicate);
// Asserts
await expect(result).resolves.toBe(response.data);
});
it('sets expected vehicle data', async () => {
// Arrange
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(fullApiResponse));

View file

@ -1,189 +1,228 @@
html {
.has-error {
// START HOVER
&.list-button-horizontal,
&.list-button,
&.list-button.list-group,
&.list-card
&.option-label {
border: 1px solid $red;
position: relative;
z-index: 4;
.has-error {
.button-content {
border: none;
}
// START HOVER
&.list-button-horizontal,
&.list-button,
&.list-button.list-group,
&.list-card &.option-label {
border: 1px solid $red;
position: relative;
z-index: 4;
&:hover {
@include box-shadow-hover($red-200);
z-index: 5;
}
input[type="radio"]:focus + .list-button-content {
box-shadow: 0 0 0 2.5px $red;
}
}
.button-content {
border: none;
}
&.list-card {
border: 1px solid $red;
}
&.ui-radio {
&:hover {
input[type="radio"],
input[type="checkbox"] {
@include box-shadow-hover($red-200);
}
}
}
.form-check-input {
&:focus {
box-shadow: 0 0 0 2.5px $red;
}
}
// END HOVER
&:hover {
@include box-shadow-hover($red-200);
z-index: 5;
}
&.list-button,
&.list-card {
color: $red;
input[type="radio"]:focus+.list-button-content {
box-shadow: 0 0 0 2.5px $red;
}
}
input[type="checkbox"]:focus + label,
input[type="radio"]:focus + label {
box-shadow: 0 0 0 2.5px $red;
}
input[type="checkbox"]:checked + label {
box-shadow: 0 0 0 1px $red;
}
&:hover {
border-radius: 0.5rem;
}
}
&.list-button-horizontal {
color: $red;
input[type="checkbox"]:focus + label,
input[type="radio"]:focus + label {
box-shadow: 0 0 1px $red;
}
}
&.list-card {
border: 1px solid $red;
}
&.grid-item {
input[type="radio"] {
+ label {
border: 1px solid $red;
&:hover {
background-color: $blue-100;
box-shadow: 0px 0px 0px 4px $red-200;
}
}
}
}
&.ui-radio,
&.ui-checkbox {
input[type="checkbox"],
input[type="radio"],
input[type="radio"] + label:before,
input[type="checkbox"] + label:before {
border: 1px solid $red;
background-color: initial;
}
input[type="checkbox"]:checked + label:before {
border: 1px solid $blue;
}
}
&.textbox-question,
&.dropdown-question {
p {
color: $red;
}
input,
input.form-control,
select,
select.form-select {
border: 1px solid $red;
&:focus {
border: 1px solid transparent;
box-shadow: 0 0 0 2.5px $red;
}
&.ui-radio {
&:hover {
&:hover {
box-shadow: 0px 0px 0px 4px $red-200;
border-radius: 0.5rem;
border: 1px solid $red;
}
}
select,
select.form-select {
background-image: url("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='%23d4281c'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 0.75rem center;
background-size: 16px 12px;
}
}
}
//Restore to default style if alert box is present
.alertError {
.has-error {
&.list-button,
&.list-card {
border: 1px solid $gray-500;
input:not(:focus) {
+ label {
border: none;
box-shadow: 0 0 0 1px $gray-500;
border-radius: 0.5rem;
}
}
input:checked:focus {
+ label {
border: none;
box-shadow: 0 0 0 2.5px $blue;
border-radius: 0.5rem;
}
}
input:checked:not(:focus) {
+ label {
box-shadow: 0 0 0 1px $blue;
border-radius: 0.5rem;
}
}
input:focus {
border: 1px solid $gray-500;
+ label {
box-shadow: 0 0 0 2.5px transparent;
}
}
&:hover {
box-shadow: 0 0 0 4px $blue-300;
+ label {
box-shadow: 0 0 0 2.5px transparent;
border: 1px solid $blue;
}
}
}
}
}
input[type="radio"],
input[type="checkbox"] {
@include box-shadow-hover($red-200);
}
}
}
.form-test-error {
color: $red;
font-size: 0.875rem;
font-weight: 500;
}
.form-check-input {
&:focus {
box-shadow: 0 0 0 2.5px $red;
}
}
.form-test-invalid {
&.btn.btn-primary {
color: $gray-600;
background: $gray-200;
cursor: pointer;
pointer-events: all;
font-weight: $font-weight-normal;
// END HOVER
&.list-button,
&.list-card {
color: $red;
input[type="checkbox"]:focus+label,
input[type="radio"]:focus+label {
box-shadow: 0 0 0 2.5px $red;
}
input[type="checkbox"]:checked+label {
box-shadow: 0 0 0 1px $red;
}
&:hover {
border-radius: 0.5rem;
}
}
&.list-button-horizontal {
color: $red;
input[type="checkbox"]:focus+label,
input[type="radio"]:focus+label {
box-shadow: 0 0 1px $red;
}
}
&.grid-item {
input[type="radio"] {
+label {
border: 1px solid $red;
&:hover {
background-color: $blue-100;
box-shadow: 0px 0px 0px 4px $red-200;
}
}
}
}
&.ui-radio,
&.ui-checkbox {
input[type="checkbox"],
input[type="radio"],
input[type="radio"]+label:before,
input[type="checkbox"]+label:before {
border: 1px solid $red;
background-color: initial;
}
input[type="checkbox"]:checked+label:before {
border: 1px solid $blue;
}
}
&.textbox-question,
&.dropdown-question {
p {
color: $red;
}
input,
input.form-control,
select,
select.form-select {
border: 1px solid $red;
&:focus {
border: 1px solid transparent;
box-shadow: 0 0 0 2.5px $red;
}
&:hover {
box-shadow: 0px 0px 0px 4px $red-200;
border-radius: 0.5rem;
border: 1px solid $red;
}
}
select,
select.form-select {
background-image: url("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='%23d4281c'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 0.75rem center;
background-size: 16px 12px;
}
}
}
&.btn.btn-primary:hover,
&.btn.btn-primary:focus {
background: $gray-200;
box-shadow: none;
//Quote page radio button group
.package-main {
.package-wrapper {
.has-error {
input[type="radio"] {
+.package-label {
border: 1px solid $red;
}
}
}
}
}
&.btn.btn-primary:focus-visible {
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $gray-700;
//Restore to default style if alert box is present
.alertError {
.has-error {
&.list-button,
&.list-card {
border: 1px solid $gray-500;
input:not(:focus) {
+label {
border: none;
box-shadow: 0 0 0 1px $gray-500;
border-radius: 0.5rem;
}
}
input:checked:focus {
+label {
border: none;
box-shadow: 0 0 0 2.5px $blue;
border-radius: 0.5rem;
}
}
input:checked:not(:focus) {
+label {
box-shadow: 0 0 0 1px $blue;
border-radius: 0.5rem;
}
}
input:focus {
border: 1px solid $gray-500;
+label {
box-shadow: 0 0 0 2.5px transparent;
}
}
&:hover {
box-shadow: 0 0 0 4px $blue-300;
+label {
box-shadow: 0 0 0 2.5px transparent;
border: 1px solid $blue;
}
}
}
}
}
}
}
.form-test-error {
color: $red;
font-size: 0.875rem;
font-weight: 500;
}
.form-test-invalid {
&.btn.btn-primary {
color: $gray-600;
background: $gray-200;
cursor: pointer;
pointer-events: all;
font-weight: $font-weight-normal;
}
&.btn.btn-primary:hover,
&.btn.btn-primary:focus {
background: $gray-200;
box-shadow: none;
}
&.btn.btn-primary:focus-visible {
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $gray-700;
}
}
}