diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e58f51862..2760d6a1a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -79,4 +79,44 @@ stages: __VUE_APP_CONSUMER_API_GATEWAY__: $(__VUE_APP_CONSUMER_API_GATEWAY__) __VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__) __VUE_APP_HERITAGE_FUNNEL__: $(__VUE_APP_HERITAGE_FUNNEL__) + indexDeployVariables: + __VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__) + __VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__) + cfDistributionId: $(cfDistributionId) + + + # QA Build/Deploy + - stage: Qa + variables: + - group: FixMyGlassQa + jobs: + - deployment: qaBuildDeployment + displayName: Build and Deploy FMG - QA + environment: digitalCloud-qa + container: node + workspace: + clean: all + strategy: + runOnce: + deploy: + steps: + - checkout: self + clean: true + - template: templates/digital/step-build-vue.yml@AzureDevOps + parameters: + buildOutputDir: dist + - template: templates/digital/step-deploy-vue.yml@AzureDevOps + parameters: + artifactName: vueDist + awsProfile: $(qaDeploymentProfile) + outputPath: /fmg/ + deployBuckets: + safelite-qa-fmg-us-east-1: + clearFolder: true + deployFolder: '' + region: us-east-1 + appDeployVariables: + __VUE_APP_CONSUMER_API_GATEWAY__: $(__VUE_APP_CONSUMER_API_GATEWAY__) + __VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__) + __VUE_APP_HERITAGE_FUNNEL__: $(__VUE_APP_HERITAGE_FUNNEL__) cfDistributionId: $(cfDistributionId) \ No newline at end of file diff --git a/jest.config.js b/jest.config.js index b387a0e5d..20e2d863e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -17,11 +17,9 @@ module.exports = { "!src/layouts/vin-lookup/vin-lookup.vue", "!src/layouts/vehicle-damage/windshield-damage-type-question/windshield-damage-type-question.vue", "!src/layouts/vehicle-damage/windshield-options/windshield-options.vue", - "!src/layouts/address-poc/address-poc.vue", - "!src/layouts/nested-radio-poc/nested-radio.vue", - "!src/layouts/button-question-examples/**/*.vue", "!src/layouts/part-questions/**/*.vue", "!src/layouts/reveal/**/*.vue", + "!src/layouts/estimate/**/*.vue", // REMOVE THESE AFTER WRITING UNIT TESTS "!src/layouts/address-lookup/customer-questions/customer-questions.vue", @@ -31,7 +29,7 @@ module.exports = { testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"], coverageThreshold: { global: { - statements: 87, + statements: 86, // Got the go ahead from Mark to temporarily lower this. Taking out initialize component made the year,make,model and style coverage drop a bit. Once unit tests for license plate lookup, vin lookup and address lookup are in the coverage should go back up to 90 }, }, diff --git a/public/index.html b/public/index.html index b926ca739..b8a80e384 100644 --- a/public/index.html +++ b/public/index.html @@ -1,6 +1,10 @@ + + @@ -11,9 +15,17 @@ <%= htmlWebpackPlugin.options.title %> + + + + +
diff --git a/src/common-components/button-question/button-question.vue b/src/common-components/button-question/button-question.vue index 79937c780..e29f1fd93 100644 --- a/src/common-components/button-question/button-question.vue +++ b/src/common-components/button-question/button-question.vue @@ -6,7 +6,9 @@
- {{groupName}} + + {{(isMultiSelect && answers && answers.length > 1) ? 'Select one or more options below.' : 'Select an option below.' }} +
diff --git a/src/common-components/funnel-footer/funnel-footer.vue b/src/common-components/funnel-footer/funnel-footer.vue index cea6b403e..abac8a91f 100644 --- a/src/common-components/funnel-footer/funnel-footer.vue +++ b/src/common-components/funnel-footer/funnel-footer.vue @@ -22,13 +22,13 @@ isPrimary :buttonText="buttonText" loaderColor="white" - :class="isDisabled && 'form-test-invalid'" - :aria-disabled="isDisabled" - :isDisabled="isDisabled" + :class="isForwardActionDisabled && 'form-test-invalid'" + :aria-disabled="isForwardActionDisabled" + :isDisabled="isForwardActionDisabled" @click-event="buttonClick" />
- @@ -38,7 +39,8 @@ /> @@ -50,7 +52,7 @@ @@ -75,8 +77,10 @@ import baseMixin from "@/mixins/base-mixin.js"; import { storeActions } from "@/constants/store-actions"; import { storeMutations } from "@/constants/store-mutations"; import { errorMessages } from "@/constants/error-messages"; +import { getDamageString, compareGlassOptions } from "@/helpers/damage-helper"; import { required, regex } from "@/helpers/validation-rules"; import { Form, defineRule } from "vee-validate"; +import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; // DEFINE VALIDATION RULES defineRule("license-plate-required", required(errorMessages.LICENSE_PLATE_REQUIRED)); @@ -118,8 +122,32 @@ export default { zip: '', email: '', serviceZip: '', + carIdEntered: '', + customAlertData: {}, + newCarId: false, + glassOptionsMismatch: false, }; }, + computed: { + MatchedDifferentVehicleAlertHeader(){ + let text = this.getCmsContent("MatchedDifferentVehicleAlertWidget", "HeadlineText").replaceAll("{custom:damage}", getDamageString()); + + return text; + }, + MatchedDifferentVehicleAlertBody(){ + let text = this.getCmsContent("MatchedDifferentVehicleAlertWidget", "BodyText").replaceAll("{custom:damage}", getDamageString()).replaceAll("{custom:plateLookupYear}", this.customAlertData?.vehicleInfo?.year).replaceAll("{custom:plateLookupMake}", this.customAlertData?.vehicleInfo?.make).replaceAll("{custom:plateLookupModel}", this.customAlertData?.vehicleInfo?.model); + + return text; + }, + NoServiceZipHeader(){ + let text = this.getCmsContent("NoServiceZipWidget", "HeadlineText").replaceAll("{custom:zip}", this.zip); + + return text; + }, + NoServiceZipBody(){ + return this.getCmsContent("NoServiceZipWidget", "BodyText"); + } + }, methods: { arePagePrerequisitesValid() { return store.getters.vehicle.carId !== null; @@ -137,43 +165,58 @@ export default { async forwardButtonAction() { const zipValidation = this.serviceZip ? await this.validateZip(this.serviceZip) : await this.validateZip(this.zip); if (!zipValidation.data.isServiceable) { + this.customAlertData.zip = this.zip; this.$refs.funnelFooter.removeLoader(); + this.vinDoesNotMatchCarId = false; + this.vinNotValid = false; this.newServiceZipRequired = true; return; - } + } + const vinLookup = await this.lookupVin(this.licensePlate, zipValidation.data.state).catch(() => { this.$refs.funnelFooter.removeLoader(); + this.vinDoesNotMatchCarId = false; this.vinNotValid = true; return; }); - if (vinLookup.data.vehicle.carId !== store.getters.vehicle.carId) { + + if ((vinLookup.data.vehicle.carId !== store.getters.vehicle.carId) && (vinLookup.data.vehicle.carId !== this.carIdEntered)) { + this.carIdEntered = vinLookup.data.vehicle.carId; + this.customAlertData.vehicleInfo = vinLookup.data.vehicle; + this.newCarId = true; + const glassOptions = await baseMixin.methods.dispatchNonBlockingStoreAction( + storeActions.GET_DAMAGE_OPTIONS, + { carId: vinLookup.data.vehicle.carId } + ); + this.glassOptionsMismatch = compareGlassOptions(glassOptions.data, store.getters.damage.glassToReplace); this.$refs.funnelFooter.updateButtonText(`Continue with ${vinLookup.data.vin} ${vinLookup.data.vehicle.year} ${vinLookup.data.vehicle.make} ${vinLookup.data.vehicle.model}`); this.$refs.funnelFooter.removeLoader(); + this.vinNotValid = false; this.vinDoesNotMatchCarId = true; return; } + this.updateCustomerInfo(vinLookup.data.vin, vinLookup.data.vehicle, zipValidation.data.state); + const partsData = await baseMixin.methods.dispatchNonBlockingStoreAction( this.storeActions.GET_PARTS_OR_QUESTIONS, { - carId: store.getters.vehicle.carId, - glassArray: store.getters.damage.glassToReplace, - zipCode: this.zip, - vin: vinLookup.vin + carId: vinLookup.data.vehicle.carId, + glassArray: store.getters.damage.glassToReplace ? store.getters.damage.glassToReplace : [], + zipCode: this.serviceZip ? this.serviceZip : this.zip, + vin: vinLookup.data.vin }, false ); this.navigateForward(partsData); }, navigateForward(partsData){ - if(partsData.data.partsOrQuestions[0].partQuestions && partsData.data.partsOrQuestions[0].partQuestions.length > 0){ - this.$router.navigateAfterSave(this.navigationScenarios.CONTINUING_WITH_PARTS_QUESTION, this.$route, {}, {}, partsData.data); + if(this.newCarId && this.glassOptionsMismatch){ + this.$router.navigateAfterSave(this.navigationScenarios.CLICKED_FORWARD, this.$route, {}, { displayVehicleChangeAlert: true }, partsData.data); return; - } else if((!partsData.data.partsOrQuestions[0].partQuestions || partsData.data.partsOrQuestions[0].partQuestions.length < 1) && partsData.data.partsOrQuestions[0].parts.length > 1) { - this.$router.navigateAfterSave(this.navigationScenarios.CONTINUING_WITH_MULTIPLE_PARTS, this.$route, {}, {}, partsData.data); - return; } else { - this.$router.navigate(this.navigationScenarios.CONTINUING_WITH_SINGLE_PART, this.$route); + navigateToHeritageFunnel(); + return; } }, validateZip(zip) { @@ -188,27 +231,32 @@ export default { { licensePlate: plate, licenseState: state } ); }, - updateStore() { - // if(vehicleDamage){ - // store.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES); - // } - store.commit(storeMutations.UPDATE_VEHICLE_VIN, null); - store.commit(storeMutations.UPDATE_YEAR, null); - store.commit(storeMutations.UPDATE_MAKE, null); - store.commit(storeMutations.UPDATE_MODEL, null); - store.commit(storeMutations.UPDATE_STYLE, null); - store.commit(storeMutations.UPDATE_CAR_ID, null); - store.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, null); - store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, null); - store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, null); - store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, null); - store.commit(storeMutations.UPDATE_REGISTRATION_LICENSE_PLATE, null); - store.commit(storeMutations.UPDATE_REGISTRATION_STATE, null); - store.commit(storeMutations.UPDATE_REGISTRATION_ZIP_CODE, null); - store.commit(storeMutations.UPDATE_SERVICE_LOCATION_ZIP, null); - store.commit(storeMutations.UPDATE_CUSTOMER_EMAIL_ADDRESS, null); + updateCustomerInfo(vin, vehicleInfo, registrationState) { + if(this.newCarId && this.glassOptionsMismatch){ + store.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES); + } + store.commit(storeMutations.UPDATE_VEHICLE_VIN, vin); + store.commit(storeMutations.UPDATE_YEAR, vehicleInfo.year); + store.commit(storeMutations.UPDATE_MAKE, vehicleInfo.make); + store.commit(storeMutations.UPDATE_MODEL, vehicleInfo.model); + store.commit(storeMutations.UPDATE_STYLE, vehicleInfo.style); + store.commit(storeMutations.UPDATE_CAR_ID, vehicleInfo.carId); + store.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, vehicleInfo.category); + store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, vehicleInfo.imageUrl); + store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, vehicleInfo.imageVifNumber); + store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, vehicleInfo.imageColor); + store.commit(storeMutations.UPDATE_REGISTRATION_LICENSE_PLATE, this.licensePlate); + store.commit(storeMutations.UPDATE_REGISTRATION_STATE, registrationState); + store.commit(storeMutations.UPDATE_REGISTRATION_ZIP_CODE, this.zip); + store.commit(storeMutations.UPDATE_SERVICE_LOCATION_ZIP, this.serviceZip); + store.commit(storeMutations.UPDATE_CUSTOMER_EMAIL_ADDRESS, this.email); }, }, + watch: { + licensePlate() { + this.$refs.funnelFooter.updateButtonText(this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")); + } + }, components: { Form, funnelHeader, diff --git a/src/layouts/part-questions/part-questions.vue b/src/layouts/part-questions/part-questions.vue index c1b9142bd..0818bb46f 100644 --- a/src/layouts/part-questions/part-questions.vue +++ b/src/layouts/part-questions/part-questions.vue @@ -1,10 +1,12 @@ @@ -49,18 +51,7 @@ export default { // Call the "next" function to complete the transition to this page. next((vm) => { - vm.$refs.funnelHeader.initializeComponent( - resultMap.cmsContent.FunnelHeaderWidget - ); - vm.$refs.vehicleBanner.initializeComponent( - resultMap.cmsContent.VehicleBannerWidget - ); - vm.$refs.funnelSubHeader.initializeComponent( - resultMap.cmsContent.FunnelSubHeaderWidget - ); - vm.$refs.funnelFooter.initializeComponent( - resultMap.cmsContent.FunnelFooterWidget - ); + vm.setCmsContent(resultMap.cmsContent); }); }, methods: { diff --git a/src/layouts/vehicle-damage/vehicle-damage.spec.js b/src/layouts/vehicle-damage/vehicle-damage.spec.js index fe3a4aa51..7b28e4bb5 100644 --- a/src/layouts/vehicle-damage/vehicle-damage.spec.js +++ b/src/layouts/vehicle-damage/vehicle-damage.spec.js @@ -36,6 +36,11 @@ jest.mock("@/store", () => ({ vehicle: { carId: "C00000000", image: "test.jpg", + payment: { + insuranceCoverage: { + isVerified: false + } + } }, eventBusItem: jest.fn(), damage: { @@ -134,6 +139,7 @@ describe("vehicle-damage.vue", () => { store: { getters: { vehicle: {}, + payment: { insuranceCoverage: { isVerified: false } }, }, }, }, @@ -201,6 +207,7 @@ describe("vehicle-damage.vue", () => { store: { getters: { vehicle: {}, + payment: { insuranceCoverage: { isVerified: false } }, }, }, }, @@ -291,6 +298,7 @@ describe("vehicle-damage.vue", () => { store: { getters: { vehicle: {}, + payment: { insuranceCoverage: { isVerified: false } }, }, }, }, @@ -336,6 +344,7 @@ describe("vehicle-damage.vue", () => { store: { getters: { vehicle: {}, + payment: { insuranceCoverage: { isVerified: false } }, }, }, }, @@ -734,6 +743,7 @@ function setupMocks({ store: { getters: { vehicle: {}, + payment: { insuranceCoverage: { isVerified: false } }, }, }, }, diff --git a/src/layouts/vehicle-damage/vehicle-damage.vue b/src/layouts/vehicle-damage/vehicle-damage.vue index b84e2fbfa..543a02bef 100644 --- a/src/layouts/vehicle-damage/vehicle-damage.vue +++ b/src/layouts/vehicle-damage/vehicle-damage.vue @@ -47,7 +47,8 @@ /> @@ -145,9 +146,11 @@ export default { } return false; }, + resetDependentState() { store.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES); }, + backButtonAction() { // route to move backwards this.$router.navigate( @@ -155,19 +158,20 @@ export default { this.$route ); }, + getDamageLocationsFromStore() { var glassSelections = []; - if (store.getters.damage.glassToReplace.some(glass => { return glass.location === damageLocationsSelected.WINDSHIELD }) || + if (store.getters.damage.glassToReplace?.some(glass => { return glass.location === damageLocationsSelected.WINDSHIELD }) || store.getters.damage.isRepair) { glassSelections.push(damageLocationsSelected.WINDSHIELD); } - if (store.getters.damage.glassToReplace.some(glass => { return glass.location === damageLocationsSelected.DRIVER || + if (store.getters.damage.glassToReplace?.some(glass => { return glass.location === damageLocationsSelected.DRIVER || glass.location === damageLocationsSelected.PASSENGER })) { glassSelections.push(damageLocationsSelected.SIDEDOOR); } - if (store.getters.damage.glassToReplace.some(glass => { return glass.location === damageLocationsSelected.REAR })) { + if (store.getters.damage.glassToReplace?.some(glass => { return glass.location === damageLocationsSelected.REAR })) { glassSelections.push(damageLocationsSelected.REARWINDOW); } @@ -180,19 +184,19 @@ export default { if (store.getters.damage.isRepair === undefined) return windshieldOptions; if (!store.getters.damage.isRepair) { - if (store.getters.damage.glassToReplace.some(glass => { return glass.location === damageLocationsSelected.WINDSHIELD && + if (store.getters.damage.glassToReplace?.some(glass => { return glass.location === damageLocationsSelected.WINDSHIELD && glass.name === damageLocationsSelected.SINGLE })) { windShieldOptions.selectedWindshieldDamageType.push(damageLocationsSelected.REPLACE); windShieldOptions.selectedWindshieldReplaceOptions.push(damageLocationsSelected.SINGLE); } - if (store.getters.damage.glassToReplace.some(glass => { return glass.location === damageLocationsSelected.WINDSHIELD && + if (store.getters.damage.glassToReplace?.some(glass => { return glass.location === damageLocationsSelected.WINDSHIELD && glass.name === damageLocationsSelected.DRIVER })) { windShieldOptions.selectedWindshieldDamageType.push(damageLocationsSelected.REPLACE); windShieldOptions.selectedWindshieldReplaceOptions.push(damageLocationsSelected.DRIVER); } - if (store.getters.damage.glassToReplace.some(glass => { return glass.location === damageLocationsSelected.WINDSHIELD && + if (store.getters.damage.glassToReplace?.some(glass => { return glass.location === damageLocationsSelected.WINDSHIELD && glass.name === damageLocationsSelected.PASSENGER })) { windShieldOptions.selectedWindshieldDamageType.push(damageLocationsSelected.REPLACE); windShieldOptions.selectedWindshieldReplaceOptions.push(damageLocationsSelected.PASSENGER); @@ -210,11 +214,11 @@ export default { getDoorSidesFromStore() { var doorSides = []; - if (store.getters.damage.glassToReplace.some(glass => { return glass.location === damageLocationsSelected.DRIVER })){ + if (store.getters.damage.glassToReplace?.some(glass => { return glass.location === damageLocationsSelected.DRIVER })){ doorSides.push(damageLocationsSelected.DRIVERSIDE); } - if (store.getters.damage.glassToReplace.some(glass => { return glass.location === damageLocationsSelected.PASSENGER })){ + if (store.getters.damage.glassToReplace?.some(glass => { return glass.location === damageLocationsSelected.PASSENGER })){ doorSides.push(damageLocationsSelected.PASSENGERSIDE); } @@ -224,7 +228,7 @@ export default { getDriverSideReplaceOptionsFromStore() { var driverSideReplaceOptions = []; - store.getters.damage.glassToReplace.forEach(glass => { + store.getters.damage.glassToReplace?.forEach(glass => { if (glass.location === damageLocationsSelected.DRIVER){ driverSideReplaceOptions.push(glass.name); } @@ -236,7 +240,7 @@ export default { getPassengerSideReplaceOptionsFromStore() { var passengerSideReplaceOptions = []; - store.getters.damage.glassToReplace.forEach(glass => { + store.getters.damage.glassToReplace?.forEach(glass => { if (glass.location === damageLocationsSelected.PASSENGER){ passengerSideReplaceOptions.push(glass.name); } @@ -248,7 +252,7 @@ export default { getRearReplaceOptionsFromStore(){ var rearReplaceOptions = []; - store.getters.damage.glassToReplace.forEach(glass => { + store.getters.damage.glassToReplace?.forEach(glass => { if (glass.location === damageLocationsSelected.REAR){ rearReplaceOptions.push(glass.name); } @@ -275,13 +279,20 @@ export default { }, navigateForward(partsData){ - // CSR-98 TEMP - const vehicleYearsToShowHeritageFunnel = [ "2001", "2002", "2010" ]; + + // Temporary easter egg to navigate to heritage funnel. + const vehicleYearsToShowHeritageFunnel = [ 2001, 2002, 2010, 2016 ]; if (vehicleYearsToShowHeritageFunnel.includes(store.getters.vehicle.year)) { navigateToHeritageFunnel(); return; } + // If vin already exists, navigate directly to vin-lookup + if(this.$store.getters.vehicle.vin){ + this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD_WITH_VIN, this.$route); + return; + } + //found problem questions if (partsData.data.partsOrQuestions.some(pq => pq.partQuestions != null && pq.partQuestions.length > 0)){ this.$router.navigateAfterSave(this.navigationScenarios.SELECTED_DAMAGE_WITH_PART_QUESTIONS, this.$route, {}, {}, partsData.data); @@ -382,7 +393,7 @@ export default { return this.isWindshieldDamageLocation && this.selectedDamageLocations.length > 1 && this.isWindshieldRepair; }, hasSplitSingleConflict() { - if (!this.selectedWindshieldOptions.selectedWindshieldReplaceOptions) return false; + if (!this.selectedDamageLocations || !this.selectedDamageLocations.includes("Windshield") || !this.selectedWindshieldOptions.selectedWindshieldDamageType || !this.selectedWindshieldOptions.selectedWindshieldDamageType.includes("Replace") || !this.selectedWindshieldOptions.selectedWindshieldReplaceOptions) return false; return this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.some(selectedSingleWindshield => { @@ -398,6 +409,9 @@ export default { }) ); }, + shouldHideBackButton(){ + return this.$store.getters.payment.insuranceCoverage.isVerified; + } }, components: { diff --git a/src/layouts/vehicle-year/vehicle-year.vue b/src/layouts/vehicle-year/vehicle-year.vue index 9e3dd7e2f..96f62409c 100644 --- a/src/layouts/vehicle-year/vehicle-year.vue +++ b/src/layouts/vehicle-year/vehicle-year.vue @@ -17,11 +17,16 @@ import yearQuestion from "@/layouts/vehicle-year/year-question/year-question"; import funnelHeader from "@/common-components/funnel-header/funnel-header"; import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner"; import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header"; + // Supporting files import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { settleAllPromises } from "@/helpers/layout-helper"; import { storeMutations } from "@/constants/store-mutations"; import { storeActions } from "@/constants/store-actions"; +import { experimentUniverses } from "@/constants/experiments"; +import { getDeviceIdValue, getSessionIdValue, getSessionKeyValue } from "@/helpers/heritage-integration/cookie-helper"; + +import baseMixin from "@/mixins/base-mixin"; import store from "@/store"; export default { @@ -36,8 +41,16 @@ export default { async beforeRouteEnter(to, from, next) { // Call APIs const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage); - const yearQuestionInitialDataPromise = - yearQuestion.methods.loadInitialData(); + const yearQuestionInitialDataPromise = yearQuestion.methods.loadInitialData(); + + // Log experiment exposure + const logExperimentExposurePromise = baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.LOG_EXPERIMENT_EXPOSURE, + { + userId: getDeviceIdValue(), + sessionKey: getSessionKeyValue(), + pageName: to.query.fmgPage, + universeName: experimentUniverses.CONCEPT_FUNNEL + }, false); // Settle promises and get results const promiseResultMap = [ @@ -49,6 +62,10 @@ export default { resultKey: "yearQuestionInitialData", promise: yearQuestionInitialDataPromise, }, + { + resultKey: "logExperimentExposure", + promise: logExperimentExposurePromise, + }, ]; let resultMap = await settleAllPromises(promiseResultMap); @@ -64,8 +81,8 @@ export default { watch: { selectedYear(year) { - - this.$store.commit(this.storeMutations.UPDATE_YEAR, year); + const parsedYear = parseInt(year); + this.$store.commit(this.storeMutations.UPDATE_YEAR, parsedYear); this.$router.navigateAfterSave( this.navigationScenarios.SELECTED_YEAR, this.$route diff --git a/src/layouts/vin-lookup/vin-lookup.vue b/src/layouts/vin-lookup/vin-lookup.vue index fc9058abf..c3a1bbc33 100644 --- a/src/layouts/vin-lookup/vin-lookup.vue +++ b/src/layouts/vin-lookup/vin-lookup.vue @@ -1,41 +1,272 @@ - - diff --git a/src/mixins/base-mixin.js b/src/mixins/base-mixin.js index b5a954c43..11947f26a 100644 --- a/src/mixins/base-mixin.js +++ b/src/mixins/base-mixin.js @@ -3,6 +3,7 @@ import { storeActions } from "@/constants/store-actions.js"; import { storeMutations } from "@/constants/store-mutations.js"; import { navigationScenarios } from "@/router/router-constants/navigation-scenarios"; import { vehicleCategories } from "@/constants/vehicle-categories.js"; +import { queryStrings } from "@/constants/query-strings"; export default { data() { @@ -11,10 +12,10 @@ export default { }; }, methods: { - setCmsContent(cmsContent){ + setCmsContent(cmsContent) { this.$root.cmsContentByWidget = cmsContent; }, - getCmsContent(widgetName, fieldName){ + getCmsContent(widgetName, fieldName) { return this.$root.cmsContentByWidget?.[widgetName]?.[fieldName] ? this.$root.cmsContentByWidget[widgetName][fieldName] : ''; }, dispatchNonBlockingStoreAction(type, payload, encodePayload = true) { @@ -25,10 +26,10 @@ export default { return store.dispatch(type, payload); }, - savePageDataToStore(page, data){ + savePageDataToStore(page, data) { store.commit(storeMutations.UPDATE_PAGE_DATA, { page: page, data: data }); }, - onSubmit() {}, // DO NOT REMOVE; needed to prevent default form submit behavior + onSubmit() { }, // DO NOT REMOVE; needed to prevent default form submit behavior onInvalidSubmit({ values, errors, results }) { // identify the first error field and put focus on it // get error names array @@ -40,6 +41,28 @@ export default { el && el.focus(); } }, + + pushEventToGA(category, action, label, value, pageName) { + const eventToBePushed = { + 'event': 'ga_event', + 'category': category, + 'action': action, + 'label': label, + 'value': value, + 'path': `/fmg/?${queryStrings.FMG_PAGE}=${pageName}` + } + pushToDataLayerIfDefined(eventToBePushed); + }, + + pushPageViewToGA(pageName) { + const pageViewEvent = { + 'event': 'logPageview', + 'pagePath': `/fmg/?${queryStrings.FMG_PAGE}=${pageName}`, + 'pageTitle': pageName + }; + pushToDataLayerIfDefined(pageViewEvent); + } + }, computed: { storeActions() { @@ -65,3 +88,9 @@ function encodeUriData(payload) { }); } } + +function pushToDataLayerIfDefined(data) { + if (window.dataLayer !== undefined) { + window.dataLayer.push(data); + } +} diff --git a/src/router/index.js b/src/router/index.js index 778c55cfc..54c0bfac2 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -4,12 +4,13 @@ import { storeActions } from "@/constants/store-actions"; import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js"; import { routingTable } from "@/router/router-constants/routing-table.js"; import { globalEvents, globalEventTypes } from "@/constants/events"; +import { queryStrings } from "@/constants/query-strings"; // Heritage integration import { isSavedSessionStillActive } from "@/helpers/heritage-integration/session-helper"; -import { updateOrCreateFunnelCookie,getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper"; +import { updateOrCreateFunnelCookie, getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper"; import { loadOrderIfPresent, saveOrder } from "@/helpers/heritage-integration/order-helper"; -import { getPageToRouteExistingOrderTo, navigateToHeritageFunnel} from "@/helpers/heritage-integration/navigation-helper"; +import { getPageToRouteExistingOrderTo, navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import baseMixin from "@/mixins/base-mixin"; import eventBus from "@/helpers/event-bus/event-bus"; @@ -35,84 +36,79 @@ const routes = [ name: "root", async beforeEnter(to, from, next) { // If we have no query string, or we don't have the FmgPage query string. - if (to.query.fmgPage === undefined) { - await GoToFunnelStartOn404(next); - } else { - try { + try { - // If the saved session has timed out, clear the session, execute 404 logic. - if (getFunnelCookie() !== null && !isSavedSessionStillActive()) { - await GoToFunnelStartOn404(next); - } - - - - // On entering the funnel "fresh", read cookie information, decide what to do next. - if (from.redirectedFrom === undefined) { - const loadOrderResponse = await loadOrderIfPresent(); - const pageToRedirectTo = await getPageToRouteExistingOrderTo(to, loadOrderResponse); - - // If getPageToRouteExistingOrderTo determines that the return user needs to - // go back to heritage funnel, send them there and stop our current navigation. - if (pageToRedirectTo === 'heritage') { - await navigateToHeritageFunnel(); - return next(false); - } - - // Assign our fmgPage so it will load normally like the other pages. - to.query.fmgPage = pageToRedirectTo; - } - - // Process funnel cookie. - updateOrCreateFunnelCookie(); - - // If we already have our route, go to it. - if (router.hasRoute(to.query.fmgPage)) { - // Since our route is already in scope, we can grab the component from it and call the arePagePrerequisitesValid function. - let component = router.getRoutes().filter((x) => x.name === to.query.fmgPage)[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 GoToFunnelStartOn404(next); - } - - return next({ name: to.query.fmgPage, query: to.query, params: to.params }); - } - - // Get route info for the given url. Names will have a 1:1 relationship with names in the Cms. - const routeData = await GetRouteInfoFromPageName(to.query.fmgPage); - - // 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)) { - await GoToFunnelStartOn404(next); - } - - // Assign current query string parameters, as well as our fmgPage one. - next({ - name: routeData[0].name, - query: Object.assign(to.query, { fmgPage: routeData[0].name }), - params: to.params - }); - } catch (error) { - console.log(error); - - // If we don't have a route, go to our 404 page. + // If the saved session has timed out, clear the session, execute 404 logic. + if (getFunnelCookie() !== null && !isSavedSessionStillActive()) { await GoToFunnelStartOn404(next); } + + // On entering the funnel "fresh", read cookie information, decide what to do next. + if (from.redirectedFrom === undefined) { + const loadOrderResponse = await loadOrderIfPresent(); + const pageToRedirectTo = await getPageToRouteExistingOrderTo(to, loadOrderResponse); + + // If getPageToRouteExistingOrderTo determines that the return user needs to + // go back to heritage funnel, send them there and stop our current navigation. + if (pageToRedirectTo === 'heritage') { + await navigateToHeritageFunnel(); + return next(false); + } + + + // Assign our fmgPage so it will load normally like the other pages. + to.query.fmgPage = pageToRedirectTo; + } + + // Process funnel cookie. + updateOrCreateFunnelCookie(); + + // If we already have our route, go to it. + if (router.hasRoute(to.query.fmgPage)) { + // Since our route is already in scope, we can grab the component from it and call the arePagePrerequisitesValid function. + let component = router.getRoutes().filter((x) => x.name === to.query.fmgPage)[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 GoToFunnelStartOn404(next); + } + + return next({ name: to.query.fmgPage, query: to.query, params: to.params }); + } + + // Get route info for the given url. Names will have a 1:1 relationship with names in the Cms. + const routeData = await GetRouteInfoFromPageName(to.query.fmgPage); + + // 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)) { + await GoToFunnelStartOn404(next); + } + + // Assign current query string parameters, as well as our fmgPage one. + next({ + name: routeData[0].name, + query: Object.assign(to.query, { fmgPage: routeData[0].name }), + params: to.params + }); + } catch (error) { + console.log(error); + + // If we don't have a route, go to our 404 page. + await GoToFunnelStartOn404(next); } }, }, @@ -125,6 +121,10 @@ const router = createRouter({ //---------------------------------------------------------- Router Functions ---------------------------------------------------------- +router.afterEach((to, from) => { + baseMixin.methods.pushPageViewToGA(to.query[queryStrings.FMG_PAGE]); +}); + router.navigate = (scenario, currentRoute, optionalQuery = {}, optionalParams = {}, optionalPageData = {}) => { navigate(scenario, currentRoute, false, optionalQuery, optionalParams, optionalPageData); } @@ -205,6 +205,13 @@ function navigateToUrl(url, optionalQuery = {}) { externalUrl.searchParams.append(queryKey, optionalQuery[queryKey]); } + ///////////////////////////////////////////////////// + // TEMP CODE FOR TESTING WITH SPECIFIC EXPERIMENTS // + ///////////////////////////////////////////////////// + if (externalUrl.search.indexOf("corid=") != -1) + externalUrl.search = externalUrl.search + '&experiments=CollectEmailOnQuote=CollectEmailOnQuote_V1=YesCollectEmail_TEST1=true,RemoveServiceAreaPage=ServAreaRemoval_V7=ServAreaNoRemove_V7_TEST=true,VINeducationV2=VINeducation_V2=NoShowVINmodalV2_CONTROL=true,ServicePackages=ServicePackages_V1=NoShowPackages_CONTROL=true,PhotoUploadRedesign=PhotoUploadRedesign_V1=CurrentPhotoUpload_CONTROL=true,ScheduleDetailsServiceType=ScheduleBeforeServiceType_V1=ServTypeThenSched_CONTROL=true'; + ///////////// END TEMP CODE ///////////////////////// + window.location.assign(externalUrl); } diff --git a/src/router/router-constants/fmgPage-values.js b/src/router/router-constants/fmgPage-values.js index f3c811de3..9abb45cd2 100644 --- a/src/router/router-constants/fmgPage-values.js +++ b/src/router/router-constants/fmgPage-values.js @@ -11,6 +11,7 @@ const fmgPageValues = { LICENSE_PLATE_LOOKUP: "license-plate-lookup", REVEAL: "reveal", ESTIMATE: "estimate", + ADDRESS_VEHICLES: "address-vehicles", }; export { fmgPageValues }; diff --git a/src/router/router-constants/navigation-scenarios.js b/src/router/router-constants/navigation-scenarios.js index da170da53..57f07a041 100644 --- a/src/router/router-constants/navigation-scenarios.js +++ b/src/router/router-constants/navigation-scenarios.js @@ -4,7 +4,9 @@ const navigationScenarios = { SELECTED_MAKE: "SELECTED_MAKE", SELECTED_STYLE: "SELECTED_STYLE", CLICKED_BACK: "CLICKED_BACK", + CLICKED_BACK_WITH_VIN: "CLICKED_BACK_WITH_VIN", CLICKED_FORWARD: "CLICKED_FORWARD", + CLICKED_FORWARD_WITH_VIN: "CLICKED_FORWARD_WITH_VIN", SELECTED_PARTS: "SELECTED_PARTS", SELECTED_DAMAGE_WITH_SINGLE_PART: "SELECTED_DAMAGE_WITH_SINGLE_PART", SELECTED_DAMAGE_WITH_MULTIPLE_PARTS: "SELECTED_DAMAGE_WITH_MULTIPLE_PARTS", diff --git a/src/router/router-constants/routing-table.js b/src/router/router-constants/routing-table.js index fd750e1a8..5b991b63e 100644 --- a/src/router/router-constants/routing-table.js +++ b/src/router/router-constants/routing-table.js @@ -1,6 +1,5 @@ import { fmgPageValues } from "@/router/router-constants/fmgPage-values"; import { navigationScenarios } from "@/router/router-constants/navigation-scenarios"; -import { applicationConfig } from "@/constants/application-config"; const routingTable = [ { @@ -59,6 +58,10 @@ const routingTable = [ destinationFmgPageValue: fmgPageValues.VEHICLE_STYLE, }, // UNDO THESE CHANGES BEFORE MERGING TO develop + { + scenario: navigationScenarios.CLICKED_FORWARD_WITH_VIN, + destinationFmgPageValue: fmgPageValues.VIN_LOOKUP, + }, { scenario: navigationScenarios.SELECTED_DAMAGE_WITH_SINGLE_PART, destinationFmgPageValue: fmgPageValues.ADDRESS_LOOKUP @@ -109,6 +112,10 @@ const routingTable = [ maps: [ { scenario: navigationScenarios.CLICKED_BACK, + destinationFmgPageValue: fmgPageValues.REVEAL, + }, + { + scenario: navigationScenarios.CLICKED_BACK_WITH_VIN, destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, }, { @@ -136,6 +143,10 @@ const routingTable = [ scenario: navigationScenarios.CONTINUING_WITH_SINGLE_PART, destinationFmgPageValue: fmgPageValues.REVEAL, }, + { + scenario: navigationScenarios.CLICKED_FORWARD, + destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, + }, ], }, { diff --git a/src/store/index.js b/src/store/index.js index 780b50c89..8b630b57e 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -45,10 +45,16 @@ const getDefaultState = () => { glassParts: null, otherParts: null }, + payment:{ + isInsurance: null, + insuranceCoverage: { + isVerified: null + } + }, referralNumber: null, referralDate: null, referralCorrelationId: null, - parentAccountNumber: null, + parentAccountNumber: 0, }, applicationUser: { eventBus: [], @@ -93,7 +99,7 @@ export const mutations = { updateVehicleVin(state, vin) { state.order.vehicle.vin = vin; }, - updateIsRepair(state, isRepair){ + updateIsRepair(state, isRepair) { state.order.damage.isRepair = isRepair; }, updateNumberOfChips(state, numberOfChips) { @@ -120,6 +126,27 @@ export const mutations = { updateParentAcctNumber(state, parentAcctNumber) { state.order.parentAccountNumber = parentAcctNumber; }, + updateIsInsurance(state, isInsurance) { + state.order.payment.isInsurance = isInsurance; + }, + updateInsuranceVerifiedStatus(state, isVerified) { + state.order.payment.insuranceCoverage.isVerified = isVerified; + }, + updateRegistrationLicensePlate(state, licensePlate){ + state.order.vehicle.registration.licensePlate = licensePlate; + }, + updateRegistrationState(state, registrationState){ + state.order.vehicle.registration.state = registrationState; + }, + updateRegistrationZipCode(state, reistrationZipCode){ + state.order.vehicle.registration.zipCode = reistrationZipCode; + }, + updateServiceLocationZip(state, serviceLocationZip){ + state.order.serviceLocation.zip = serviceLocationZip; + }, + updateCustomerEmailAddress(state, customerEmailAddress){ + state.order.customer.emailAddress = customerEmailAddress; + }, // EVENT BUS MUTATIONS @@ -176,23 +203,30 @@ export const mutations = { state.order.referralNumber = orderInformation.referralNumber; state.order.referralDate = orderInformation.referralDate; state.order.referralCorrelationId = orderInformation.referralCorrelationId; - state.order.vehicle = { + + state.order.vehicle = Object.assign(state.order.vehicle, { year: orderInformation.vehicle?.year, make: orderInformation.vehicle?.make, model: orderInformation.vehicle?.model, style: orderInformation.vehicle?.style, + vin: orderInformation.vehicle?.vin, carId: orderInformation.vehicle?.carId, category: orderInformation.vehicle?.category, imageUrl: orderInformation.vehicle?.imageUrl, imageVifNumber: orderInformation.vehicle?.imageVifNumber, - imageColor: orderInformation.vehicle?.imageVifColor - }; + imageColor: orderInformation.vehicle?.imageVifColor, + }); + state.order.damage.glassToReplace = orderInformation.glassToReplace; state.order.damage.isRepair = orderInformation.isRepair; state.order.damage.numberOfChips = orderInformation.numberOfChips; + state.order.lineItems.glassParts = orderInformation.parts; state.order.parentAccountNumber = orderInformation.parentAccountNumber; - state.order.serviceLocation.zipCode = orderInformation.zipCode; // TODO CSR-416 Make sure this is correct + state.order.serviceLocation.zipCode = orderInformation.zipCode; + + state.order.payment.isInsurance = orderInformation.IsInsuranceOrder; + state.order.payment.insuranceCoverage.isVerified = orderInformation?.insuranceInfo.coverageVerified; } } @@ -210,11 +244,10 @@ export const getters = { eventBus: (state) => state.applicationUser.eventBus, damage: (state) => state.order.damage, lineItems: (state) => state.order.lineItems, - pageData: (state) => (page) => { - return state.applicationUser.pageData[page]; - }, + pageData: (state) => (page) => { return state.applicationUser.pageData[page]; }, applicationUser: (state) => state.applicationUser, order: (state) => state.order, + payment: (state) => state.order.payment, } // Export Actions @@ -248,7 +281,7 @@ export const actions = { method: endpoints.LookupVinByPlate.method, endpoint: endpoints.LookupVinByPlate.url, payload: { - licensePlate: licensePlate, + licensePlate: licensePlate, licenseState: licenseState }, }); @@ -304,12 +337,12 @@ export const actions = { }, getDamageOptions(context, { carId }) { return globalMethods.callHttpClient({ - methods: endpoints.GetDamageOptions.method, + methods: endpoints.GetDamageOptions.method, endpoint: `${endpoints.GetDamageOptions.url}/${carId}`, payload: {}, }); }, - validateZip(context, {zip}) { + validateZip(context, { zip }) { return globalMethods.callHttpClient({ methods: endpoints.ValidateZip.method, endpoint: `${endpoints.ValidateZip.url}/${zip}` @@ -375,6 +408,19 @@ export const actions = { context.commit(storeMutations.UPDATE_REFERRAL_CORRELATION_ID, referralCorrelationId); }, + logExperimentExposure(context, { userId, sessionKey, pageName, universeName }) { + return globalMethods.callHttpClient({ + method: endpoints.LogExperimentExposureIfAssigned.method, + endpoint: endpoints.LogExperimentExposureIfAssigned.url, + payload: { + userId: userId, + sessionKey: sessionKey, + pageName: pageName, + universeName: universeName + } + }); + }, + // Parts API Actions getPartsOrQuestions(context, { carId, glassArray, zipCode, vin = '' }) { return globalMethods.callHttpClient({ @@ -414,16 +460,18 @@ export const actions = { }); }, - loadOrder(context, { referralNumber, referralDate, referralCorrelationId }) { + loadOrder(context, { referralNumber, referralDate, referralCorrelationId, accountNumber}) { return globalMethods.callHttpClient({ method: endpoints.LoadOrder.method, endpoint: endpoints.LoadOrder.url, payload: { referralNumber: referralNumber, referralDate: referralDate, - referralCorrelationId: referralCorrelationId + referralCorrelationId: referralCorrelationId, + accountNumber: accountNumber }, }).then((response) => { + context.commit(storeMutations.RESET_STATE); context.commit(storeMutations.UPDATE_STATE_WITH_ORDER_INFORMATION, response.data); return response; }); diff --git a/src/store/store.spec.js b/src/store/store.spec.js index 08db50bd0..7618cc25a 100644 --- a/src/store/store.spec.js +++ b/src/store/store.spec.js @@ -222,6 +222,7 @@ describe("Mutations", () => { numberOfChips: 0, parts: [], parentAccountNumber: "123456789", + insuranceInfo: {} }); // Assert @@ -232,6 +233,17 @@ describe("Mutations", () => { expect(storeState.order.vehicle.model).toEqual("ILX"); }); + it("updateInsuranceVerifiedStatus, should set isVerified flag", () => { + // Arrange + const storeState = state; + + // Act + mutations.updateInsuranceVerifiedStatus(storeState, true); + + // Assert + expect(storeState.order.payment.insuranceCoverage.isVerified).toEqual(true); + }); + }); describe("Actions", () => { @@ -690,4 +702,15 @@ describe("Getters", () => { }); + it("Payment getter, should return payment data", () => { + // Arrange + const storeState = state; + + //Act + mutations.updateInsuranceVerifiedStatus(storeState, true ); + + //Assert + expect(getters.payment(storeState).insuranceCoverage.isVerified).toEqual(true); + }); + }); \ No newline at end of file diff --git a/src/styles/common-error-styles.scss b/src/styles/common-error-styles.scss index fe1637e43..165101177 100644 --- a/src/styles/common-error-styles.scss +++ b/src/styles/common-error-styles.scss @@ -1,92 +1,126 @@ -.has-error { - &.list-button, - &.list-card { - border: 1px solid $red; - color: $red; - input[type=checkbox]:focus + label, - input[type=radio]:focus + label { - box-shadow: 0 0 0 2.5px $red; - } - input[type=checkbox]:focus + label:hover, - input[type=radio]:focus + label { - box-shadow: 0 0 0 1px $red; - } - &:hover { - box-shadow: 0px 0px 0px 4px $red-200; - border-radius: 10px !important; - border: 1px solid $red; - } - } - - input[type=checkbox]:checked + label, - input[type=radio]:checked + label { - box-shadow: 0 0 0 2.5px transparent !important; - } - &.list-button-horizontal { - color: $red; - label { +html { + .has-error { + &.list-button, + &.list-card { border: 1px solid $red; + 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 { box-shadow: 0px 0px 0px 4px $red-200; + border-radius: 10px; } } - input[type=checkbox]:focus + label, - input[type=radio]:focus + label { - box-shadow: 0 0 1px $red !important; - } - } - &.ui-radio, - &.ui-checkbox { - input[type=checkbox], - input[type=radio], - input[type=radio]+label:before, - input[type=checkbox]+label:before { - border: 1px solid $red; - } - input[type=checkbox]:checked + label:before { - border: 1px solid $blue; - } - } - &.textbox-question, - &.dropdown-question { - p { + &.list-button-horizontal { color: $red; - } - input, - select { - border: 1px solid $red; - &:focus { - border: 1px solid transparent; + label { + border: 1px solid $red; + &:hover { + box-shadow: 0px 0px 0px 4px $red-200; + } + } + input[type=checkbox]:focus + label, + input[type=radio]:focus + label { + box-shadow: 0 0 1px $red; } } - 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; + &.ui-radio, + &.ui-checkbox { + input[type=checkbox], + input[type=radio], + input[type=radio]+label:before, + input[type=checkbox]+label:before { + border: 1px solid $red; + } + input[type=checkbox]:checked + label:before { + border: 1px solid $blue; + } + } + &.textbox-question, + &.dropdown-question { + p { + color: $red; + } + input, + select { + border: 1px solid $red; + &:focus { + border: 1px solid transparent; + } + } + 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 { + box-shadow: 0 0 0 1px $gray-500; + border-radius: .5rem; + } + } + input:checked:focus { + + label { + box-shadow: 0 0 0 1px $blue; + border-radius: .5rem; + } + } + input:checked:not(:focus) { + + label { + box-shadow: 0 0 0 1px $blue; + border-radius: .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: .875rem; + font-weight: 500; + } + + .form-test-invalid { + &.btn.btn-primary { + color: $gray; + background: $gray-200; + cursor: pointer; + pointer-events: all; + } + &.btn.btn-primary:hover, + &.btn.btn-primary:focus, + &.btn.btn-primary:focus-visible { + color: $gray !important; + background: $gray-200; + box-shadow: none; } } } - -.form-test-error { - color: $red; - font-size: .875rem; - font-weight: 500; -} - -.form-test-invalid { - &.btn.btn-primary { - color: $gray; - background: $gray-200; - cursor: pointer; - pointer-events: all; - } - &.btn.btn-primary:hover, - &.btn.btn-primary:focus, - &.btn.btn-primary:focus-visible { - color: $gray !important; - background: $gray-200 !important; - box-shadow: none !important; - } - -} diff --git a/src/ux-components/alert/alert.spec.js b/src/ux-components/alert/alert.spec.js index 8072f8d73..17ed6988a 100644 --- a/src/ux-components/alert/alert.spec.js +++ b/src/ux-components/alert/alert.spec.js @@ -1,4 +1,4 @@ -import { shallowMount } from "@vue/test-utils"; +import { shallowMount } from "@vue/test-utils"; import alert from "./alert"; describe("alert.vue", () => { @@ -9,6 +9,13 @@ describe("alert.vue", () => { propsData: { isDismissible: true }, + computed: { + splitAlertCopyForLink: { + get() { + return "TEST"; + }, + } + }, mixins: [mockMixin] }); @@ -24,6 +31,13 @@ describe("alert.vue", () => { propsData: { alertClass: 'warning' }, + computed: { + splitAlertCopyForLink: { + get() { + return "TEST"; + }, + } + }, mixins: [mockMixin] }); diff --git a/src/ux-components/alert/alert.vue b/src/ux-components/alert/alert.vue index 515658e16..7cd719ba7 100644 --- a/src/ux-components/alert/alert.vue +++ b/src/ux-components/alert/alert.vue @@ -5,7 +5,15 @@ :class="[isDismissible ? 'alert-dismissible' : '', this.alertClass]" >

{{ alertHeadline }}

-

{{ alertCopy }}

+

+ +

+