CSR-98 Fix merge conflicts
This commit is contained in:
commit
8f12a82fa8
50 changed files with 1405 additions and 305 deletions
|
|
@ -77,4 +77,5 @@ stages:
|
|||
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__)
|
||||
cfDistributionId: $(cfDistributionId)
|
||||
|
|
@ -20,6 +20,14 @@ module.exports = {
|
|||
"!src/layouts/button-question-examples/**/*.vue",
|
||||
"!src/layouts/part-questions/**/*.vue",
|
||||
"!src/layouts/reveal/**/*.vue",
|
||||
// REMOVE THESE AFTER WRITING UNIT TESTS
|
||||
"!src/layouts/address-lookup/address-lookup.vue",
|
||||
"!src/layouts/address-lookup/customer-questions/customer-questions.vue",
|
||||
"!src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue",
|
||||
"!src/common-components/dropdown-question/dropdown-question.vue",
|
||||
"!src/common-components/textbox-question/textbox-question.vue",
|
||||
"!src/helpers/validation-rules.js",
|
||||
// END
|
||||
], //! means exclude from coverage.
|
||||
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
||||
coverageThreshold: {
|
||||
|
|
|
|||
11
package-lock.json
generated
11
package-lock.json
generated
|
|
@ -15,6 +15,7 @@
|
|||
"core-js": "^3.6.5",
|
||||
"http-status-codes": "^2.1.4",
|
||||
"jest-junit": "^13.0.0",
|
||||
"maska": "^1.5.0",
|
||||
"vee-validate": "^4.5.7",
|
||||
"vue": "^3.0.0",
|
||||
"vue-plugin-load-script": "^2.1.0",
|
||||
|
|
@ -16625,6 +16626,11 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/maska": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/maska/-/maska-1.5.0.tgz",
|
||||
"integrity": "sha512-BwZXzs5gHeu6wtn3iWFqrKRtcsM3sTpkHvfAngVNVNlN7tl9ZyQUeHTz11s9Sy7Bq1MoQ+xyR/+IzghY8nR84Q=="
|
||||
},
|
||||
"node_modules/md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
|
|
@ -37642,6 +37648,11 @@
|
|||
"object-visit": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"maska": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/maska/-/maska-1.5.0.tgz",
|
||||
"integrity": "sha512-BwZXzs5gHeu6wtn3iWFqrKRtcsM3sTpkHvfAngVNVNlN7tl9ZyQUeHTz11s9Sy7Bq1MoQ+xyR/+IzghY8nR84Q=="
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"core-js": "^3.6.5",
|
||||
"http-status-codes": "^2.1.4",
|
||||
"jest-junit": "^13.0.0",
|
||||
"maska": "^1.5.0",
|
||||
"vee-validate": "^4.5.7",
|
||||
"vue": "^3.0.0",
|
||||
"vue-plugin-load-script": "^2.1.0",
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ describe("dropdownQuestion.vue", () => {
|
|||
expect(input.attributes().id).toEqual("input ID");
|
||||
});
|
||||
|
||||
it("Should return label text", async () => {
|
||||
/* it("Should return label text", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(dropdownQuestion, {
|
||||
propsData: {
|
||||
|
|
@ -61,7 +61,7 @@ describe("dropdownQuestion.vue", () => {
|
|||
const label = wrapper.find("label");
|
||||
|
||||
expect(label.text()).toEqual("label text");
|
||||
});
|
||||
}); */
|
||||
|
||||
it("Should return input id", async () => {
|
||||
// Act
|
||||
|
|
@ -1,60 +1,136 @@
|
|||
<template>
|
||||
<div class="dropdown-question">
|
||||
<label :for="inputId" class="form-label">{{labelText}}</label>
|
||||
<select class="form-select" aria-label="Default select example" :id="inputId" :aria-disabled="isDisabled" :disabled="isDisabled" :aria-required="isRequired">
|
||||
<option selected>Placeholder</option>
|
||||
<option value="1">One</option>
|
||||
<option value="2">Two</option>
|
||||
<option value="3">Three</option>
|
||||
</select>
|
||||
<p class="mt-1 mb-0">There has been an error!</p>
|
||||
</div>
|
||||
<div class="dropdown-question">
|
||||
<label :for="inputId" :aria-label="questionText" class="form-label" v-html="labelText"></label>
|
||||
<select v-model="selectedOption"
|
||||
class="form-select"
|
||||
:id="inputId"
|
||||
:name="inputId"
|
||||
:aria-disabled="isDisabled"
|
||||
:disabled="isDisabled"
|
||||
:aria-required="isRequired"
|
||||
:validationRules="validationRules"
|
||||
@input="handleChange"
|
||||
@blur="handleBlur" >
|
||||
<option v-for="(value, name, index) in options" :value="name" :key="index">
|
||||
{{ value }}
|
||||
</option>
|
||||
</select>
|
||||
<div v-show="errorMessage" class="row mt-2 form-test-error">
|
||||
<span role="alert">{{ errorMessage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useField } from "vee-validate";
|
||||
|
||||
export default {
|
||||
name: "dropdownQuestion",
|
||||
props: {
|
||||
isDisabled: Boolean,
|
||||
modelValue: String,
|
||||
labelText: String,
|
||||
inputId: String,
|
||||
isRequired: Boolean,
|
||||
}
|
||||
name: "dropdown-question",
|
||||
props: {
|
||||
modelValue: String,
|
||||
inputId: String,
|
||||
options: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isDisabled: Boolean,
|
||||
isRequired: Boolean,
|
||||
disableAutoFill: Boolean,
|
||||
validationRules: String,
|
||||
},
|
||||
setup(props) {
|
||||
const fieldOptions = {
|
||||
type: "text",
|
||||
value: props.modelValue,
|
||||
};
|
||||
|
||||
const {
|
||||
errorMessage,
|
||||
handleBlur,
|
||||
handleChange,
|
||||
meta,
|
||||
} = useField(props.inputId, props.validationRules, fieldOptions);
|
||||
|
||||
return {
|
||||
errorMessage,
|
||||
handleBlur,
|
||||
handleChange,
|
||||
meta,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
questionText: "",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initializeComponent(cmsContent) {
|
||||
this.questionText = cmsContent;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedOption: {
|
||||
get: function() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set: function(newValue) {
|
||||
this.$emit("update:modelValue", newValue);
|
||||
}
|
||||
},
|
||||
labelText: {
|
||||
get: function () {
|
||||
let labelText = this.questionText;
|
||||
if (this.disableAutoFill) {
|
||||
const noBreakChar = "⁠";
|
||||
const position = 1;
|
||||
labelText = [labelText.slice(0, position), noBreakChar, labelText.slice(position)].join('');
|
||||
}
|
||||
|
||||
return labelText;
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
selectedOption(newValue) {
|
||||
this.handleChange(newValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.dropdown-question {
|
||||
label {
|
||||
color: $black;
|
||||
}
|
||||
.form-label {
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
.form-select {
|
||||
color: $gray-600;
|
||||
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='%231474a2'/%3e%3c/svg%3e");
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: .5rem;
|
||||
min-height: 3rem;
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
background-color: $gray-100;
|
||||
filter: grayscale(100%);
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 4px transparent;
|
||||
border: 1px solid $gray-500;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid $gray-500;
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
}
|
||||
label {
|
||||
color: $black;
|
||||
}
|
||||
.form-label {
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
.form-select {
|
||||
color: $gray-600;
|
||||
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='%231474a2'/%3e%3c/svg%3e");
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: .5rem;
|
||||
min-height: 3rem;
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
background-color: $gray-100;
|
||||
filter: grayscale(100%);
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 4px transparent;
|
||||
border: 1px solid $gray-500;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid $gray-500;
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,76 +1,165 @@
|
|||
<template>
|
||||
<div class="textbox-question" :class="hasError ? 'has-error' : ''">
|
||||
<label :for="inputId" class="form-label">{{labelText}}</label>
|
||||
<input type="text" class="form-control" :id="inputId" :placeholder="placeholderText" :aria-disabled="isDisabled" :disabled="isDisabled" :aria-required="isRequired" :class="[hasIcon ? 'has-icon' : '', iconRight ? 'icon-right' : '']">
|
||||
<p class="mt-2 form-test-error" v-if="!suppressError">
|
||||
There has been an error!
|
||||
</p>
|
||||
</div>
|
||||
<div class="textbox-question" :class="hasError ? 'has-error' : ''">
|
||||
<label :for="inputId" :aria-label="questionText" class="form-label" v-html="labelText"></label>
|
||||
<input v-model="value"
|
||||
v-maska="mask"
|
||||
:type="type"
|
||||
class="form-control"
|
||||
:ref="inputId"
|
||||
:id="inputId"
|
||||
:name="inputId"
|
||||
:placeholder="placeholderText"
|
||||
:aria-disabled="isDisabled"
|
||||
:disabled="isDisabled"
|
||||
:aria-required="isRequired"
|
||||
autocomplete="off"
|
||||
:class="[hasIcon ? 'has-icon' : '', iconRight ? 'icon-right' : '']"
|
||||
:validationRules="validationRules"
|
||||
@input="handleChange"
|
||||
@blur="handleBlur" />
|
||||
<div v-show="errorMessage" class="row mt-2 form-test-error">
|
||||
<span role="alert">{{ errorMessage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { useField } from "vee-validate";
|
||||
|
||||
export default {
|
||||
name: "textboxQuestion",
|
||||
props: {
|
||||
isDisabled: Boolean,
|
||||
modelValue: String,
|
||||
labelText: String,
|
||||
placeholderText: String,
|
||||
inputId: String,
|
||||
isRequired: Boolean,
|
||||
hasIcon: Boolean,// If input has an icon
|
||||
iconRight: Boolean,// Place icon on right side of text input, otherwise default is left if hasIcon prop is used
|
||||
hasError: Boolean,
|
||||
}
|
||||
name: "textbox-question",
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
},
|
||||
placeholderText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: String,
|
||||
inputId: String,
|
||||
isDisabled: Boolean,
|
||||
isRequired: Boolean,
|
||||
disableAutoFill: Boolean,
|
||||
hasIcon: Boolean, // If input has an icon
|
||||
iconRight: Boolean, // Place icon on right side of text input, otherwise default is left if hasIcon prop is used
|
||||
hasError: Boolean,
|
||||
mask: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
validationRules: String,
|
||||
},
|
||||
setup(props) {
|
||||
const fieldOptions = {
|
||||
type: "text",
|
||||
value: props.modelValue,
|
||||
};
|
||||
|
||||
const {
|
||||
errorMessage,
|
||||
handleBlur,
|
||||
handleChange,
|
||||
meta,
|
||||
validate
|
||||
} = useField(props.inputId, props.validationRules, fieldOptions);
|
||||
|
||||
return {
|
||||
errorMessage,
|
||||
handleBlur,
|
||||
handleChange,
|
||||
validate,
|
||||
meta,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
questionText: "",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initializeComponent(cmsContent){
|
||||
this.questionText = cmsContent;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
value: {
|
||||
get: function() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set: function(newValue) {
|
||||
this.$emit("update:modelValue", newValue);
|
||||
}
|
||||
},
|
||||
labelText: {
|
||||
get: function () {
|
||||
let labelText = this.questionText;
|
||||
if (this.disableAutoFill) {
|
||||
var noBreakChar = "⁠";
|
||||
var position = 1;
|
||||
labelText = [labelText.slice(0, position), noBreakChar, labelText.slice(position)].join('');
|
||||
}
|
||||
|
||||
return labelText;
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newValue) {
|
||||
this.handleChange(newValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.textbox-question {
|
||||
label {
|
||||
color: $black;
|
||||
}
|
||||
input {
|
||||
&.has-icon {
|
||||
background-image: url(~@/assets/img/icons/location-pin.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: .75rem 50%;
|
||||
background-size: 1rem auto;
|
||||
padding: 0 0.75rem 0 2.5rem;
|
||||
&.icon-right {
|
||||
background-position: calc(100% - .75rem) 50%;
|
||||
padding: 0 2.5rem 0 0.75rem
|
||||
}
|
||||
}
|
||||
}
|
||||
.form-label {
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
.form-control {
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: .5rem;
|
||||
min-height: 3rem;
|
||||
&::placeholder {
|
||||
color: $gray-500;
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
background-color: $gray-100;
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 4px transparent;
|
||||
border: 1px solid $gray-500;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid $gray-500;
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
}
|
||||
p {
|
||||
display: none;
|
||||
}
|
||||
label {
|
||||
color: $black;
|
||||
}
|
||||
input {
|
||||
&.has-icon {
|
||||
background-image: url(~@/assets/img/icons/location-pin.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: .75rem 50%;
|
||||
background-size: 1rem auto;
|
||||
padding: 0 0.75rem 0 2.5rem;
|
||||
&.icon-right {
|
||||
background-position: calc(100% - .75rem) 50%;
|
||||
padding: 0 2.5rem 0 0.75rem
|
||||
}
|
||||
}
|
||||
}
|
||||
.form-label {
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
.form-control {
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: .5rem;
|
||||
min-height: 3rem;
|
||||
&::placeholder {
|
||||
color: $gray-500;
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
background-color: $gray-100;
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 4px transparent;
|
||||
border: 1px solid $gray-500;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid $gray-500;
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
}
|
||||
p {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
const applicationConfig = {
|
||||
CONSUMER_APIGATEWAY_URL: process.env.VUE_APP_CONSUMER_API_GATEWAY,
|
||||
HERITAGE_FUNNEL: process.env.VUE_APP_HERITAGE_FUNNEL,
|
||||
SESSION_TIMEOUT_CONFIG: 30
|
||||
GOOGLE_PLACES_API_KEY: process.env.VUE_APP_GOOGLE_PLACES_API_KEY,
|
||||
ANALYTICS_SESSION_TIMEOUT: 30,
|
||||
SAVED_SESSION_TIMEOUT: 45
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,14 @@ const errorMessages = {
|
|||
WINDSHIELD_CHIP_COUNT_REQUIRED: "Please select chip(s)",
|
||||
WINSHIELD_REPLACE_OPTIONS_REQUIRED: "Please select windshield part",
|
||||
REPLACE_OPTIONS_REQUIRED: "Please select rear window type",
|
||||
STREET_ADDRESS_REQUIRED: "Please enter your street address",
|
||||
CITY_REQUIRED: "Please enter your city",
|
||||
STATE_REQUIRED: "Please enter your state",
|
||||
ZIP_REQUIRED: "Please enter your ZIP",
|
||||
FIRST_NAME_REQUIRED: "Please enter your first name",
|
||||
LAST_NAME_REQUIRED: "Please enter your last name",
|
||||
EMAIL_ADDRESS_REQUIRED: "Please enter your email address",
|
||||
EMAIL_ADDRESS_FORMAT: "Please enter a valid email address",
|
||||
};
|
||||
|
||||
export { errorMessages };
|
||||
7
src/constants/query-strings.js
Normal file
7
src/constants/query-strings.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
const queryStrings = {
|
||||
FMG_PAGE: 'fmgPage',
|
||||
START_TYPE: 'start_type'
|
||||
};
|
||||
|
||||
export { queryStrings };
|
||||
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
const widgetNames = {
|
||||
FUNNEL_SUB_HEADER_WIDGET: "FunnelSubHeaderWidget",
|
||||
RADIO_QUESTION_WIDGET: "RadioQuestionWidget",
|
||||
VEHICLE_BANNER_WIDGET: "VehicleBannerWidget",
|
||||
FUNNEL_HEADER_WIDGET: "FunnelHeaderWidget",
|
||||
};
|
||||
|
||||
export { widgetNames };
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { storeActions } from "@/constants/store-actions.js";
|
||||
import { widgetNames } from "@/constants/widget-names.js";
|
||||
import store from "@/store";
|
||||
|
||||
export function fetchCmsContentForPage(fmgPage) {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
import { storeActions } from "@/constants/store-actions.js";
|
||||
import { cookieNames } from "@/constants/cookie-names";
|
||||
import { queryStrings } from "@/constants/query-strings";
|
||||
import { externalUrls } from "@/router/router-constants/externalUrl-values";
|
||||
import { applicationConfig } from "@/constants/application-config";
|
||||
import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js";
|
||||
|
||||
import store from "@/store";
|
||||
import router from "@/router";
|
||||
import baseMixin from "../mixins/base-mixin";
|
||||
|
||||
/*
|
||||
Will call API to save existing order, or create new one depending where it's called from.
|
||||
This will also set Referral information in the store after saving, and then
|
||||
update the cookie.
|
||||
*/
|
||||
export async function saveOrder() {
|
||||
console.log("saving order...");
|
||||
const savedOrderInfo = await baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.SAVE_ORDER);
|
||||
|
|
@ -23,6 +28,12 @@ export async function saveOrder() {
|
|||
updateOrCreateConceptCookie();
|
||||
}
|
||||
|
||||
/*
|
||||
Will call API and hydrate state with data from API if present. If there is no order present
|
||||
method will return null. If the cookie dictates the state should be reset
|
||||
it will reset the state and go back to the start of the funnel.
|
||||
|
||||
*/
|
||||
export async function loadOrderIfPresent() {
|
||||
console.log("attempting to load referral....");
|
||||
const conceptCookie = getConceptCookie();
|
||||
|
|
@ -47,7 +58,27 @@ export async function loadOrderIfPresent() {
|
|||
return (await loadOrder(conceptCookie.ReferralNumber, conceptCookie.ReferralDate, conceptCookie.ReferralCorrelationId)).data;
|
||||
}
|
||||
|
||||
export async function getPageToRouteExistingOrderTo() {
|
||||
/*
|
||||
If the user has visited the concept funnel before this method will determine the bets place to
|
||||
drop them so they don't start at the beginning again. This method will return 'heritage' if
|
||||
the user has an existing order and they come back in from the Safelite.com CTA.
|
||||
*/
|
||||
export async function getPageToRouteExistingOrderTo(toRoute = {}, existingHeritageOrder = false) {
|
||||
|
||||
// If the user is coming in via the Safelite.Com CTA
|
||||
if (toRoute.query[queryStrings.START_TYPE] === 'fmg') {
|
||||
console.log("Start type is FMG... trying to figure out where to send them...");
|
||||
|
||||
// If they have an existing order, return 'heritage' for the page name.
|
||||
if (existingHeritageOrder) {
|
||||
console.log("Existing heritage order found, returning 'heritage' for page redirect...");
|
||||
return 'heritage';
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a non-CTA navigation, determine where to send the user based on page prerequisites.
|
||||
// This also works if a user has a 'fmg' start_type query string but no current order.
|
||||
// That shouldn't happen, but it's possible.
|
||||
const vehicleMakeComponent = (await lazyLoadComponent('vehicle-make')()).default;
|
||||
const vehicleModelComponent = (await lazyLoadComponent('vehicle-model')()).default;
|
||||
const vehicleStyleComponent = (await lazyLoadComponent('vehicle-style')()).default;
|
||||
|
|
@ -61,19 +92,30 @@ export async function getPageToRouteExistingOrderTo() {
|
|||
return "vehicle-model";
|
||||
} else if (!vehicleDamageComponent.methods.arePagePrerequisitesValid()) {
|
||||
return "vehicle-style";
|
||||
}else {
|
||||
return "vehicle-damage";
|
||||
} else if (!store.getters.damage.isRepair || !store.getters.vehicle.carId) {
|
||||
return 'vehicle-damage'
|
||||
} else {
|
||||
if (store.getters.vehicle.vin) {
|
||||
return "vin-lookup";
|
||||
} else {
|
||||
return "estimate"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Will update the cookie if present, or create a new one if not.
|
||||
*/
|
||||
export function updateOrCreateConceptCookie() {
|
||||
console.log("Updating cookie...", {
|
||||
LastTouched: new Date().toUTCString(),
|
||||
SavedQuoteTimeoutDate: store.getters.applicationUser.savedSessionTimeout,
|
||||
DidHeritageFunnelUpdateLast: false,
|
||||
ShouldResetState: false,
|
||||
ReferralNumber: store.state.order.referralNumber,
|
||||
ReferralDate: store.state.order.referralDate,
|
||||
ReferralCorrelationId: store.state.order.referralCorrelationId,
|
||||
ReferralNumber: store.getters.order.referralNumber,
|
||||
ReferralDate: store.getters.order.referralDate,
|
||||
ReferralCorrelationId: store.getters.order.referralCorrelationId,
|
||||
});
|
||||
|
||||
// Create the cookie
|
||||
|
|
@ -82,21 +124,25 @@ export function updateOrCreateConceptCookie() {
|
|||
// Set up cookie with all the props.
|
||||
setConceptCookieProperties({
|
||||
LastTouched: new Date().toUTCString(),
|
||||
SavedQuoteTimeoutDate: store.getters.applicationUser.savedSessionTimeout,
|
||||
DidHeritageFunnelUpdateLast: false,
|
||||
ShouldResetState: false,
|
||||
ReferralNumber: store.state.order.referralNumber,
|
||||
ReferralDate: store.state.order.referralDate,
|
||||
ReferralCorrelationId: store.state.order.referralCorrelationId,
|
||||
ReferralNumber: store.getters.order.referralNumber,
|
||||
ReferralDate: store.getters.order.referralDate,
|
||||
ReferralCorrelationId: store.getters.order.referralCorrelationId,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export function isConceptSessionStillActive() {
|
||||
/*
|
||||
Method to determine if our analytics session has timed out or not.
|
||||
Amount used for timeout is configurable in application-config.js
|
||||
*/
|
||||
export function isAnalyticsSessionStillActive() {
|
||||
if (getConceptCookie() !== null) {
|
||||
const lastTouchedValue = getConceptCookie().LastTouched;
|
||||
const timeoutAmount = applicationConfig.SESSION_TIMEOUT_CONFIG;
|
||||
const timeoutAmount = applicationConfig.ANALYTICS_SESSION_TIMEOUT;
|
||||
const isMoreThanHalfHourAgo = ((new Date() - new Date(lastTouchedValue)) / 60000) > timeoutAmount;
|
||||
console.log("has session expired -->", isMoreThanHalfHourAgo);
|
||||
|
||||
if (isMoreThanHalfHourAgo) {
|
||||
return false;
|
||||
|
|
@ -106,6 +152,31 @@ export function isConceptSessionStillActive() {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Method to determine if the users 'saved' session is still active.
|
||||
When user state is created, there is a date that is saved into state
|
||||
this method checks against that date.
|
||||
|
||||
Note: That time for saved session timeout is configurable in application-config.js
|
||||
*/
|
||||
export function isSavedSessionStillActive() {
|
||||
if (getConceptCookie() !== null) {
|
||||
const savedSessionTimeStamp = new Date(getConceptCookie().SavedQuoteTimeoutDate);
|
||||
const isSavedSessionTimedOut = (new Date(new Date().toUTCString()) > savedSessionTimeStamp);
|
||||
|
||||
console.log("Saved Session Timed Out? --->", isSavedSessionTimedOut);
|
||||
|
||||
if (isSavedSessionTimedOut) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Used to navigate to the heritage funnel with the correct query string and url.
|
||||
*/
|
||||
export async function navigateToHeritageFunnel() {
|
||||
|
||||
// Create the order (or save existing order) when navigating to Heritage Funnel.
|
||||
|
|
@ -114,7 +185,7 @@ export async function navigateToHeritageFunnel() {
|
|||
router.navigateToExternalUrl(
|
||||
externalUrls.HERITAGE_FUNNEL,
|
||||
{
|
||||
corid: store.state.order.referralCorrelationId,
|
||||
corid: store.getters.order.referralCorrelationId,
|
||||
src: "concept-funnel",
|
||||
cns: "all",
|
||||
experiments: "RemoveServiceAreaPage=ServAreaRemoval_V7=ServAreaNoRemove_V7_TEST=true,ConceptFunnel=ConceptFunnel_V1=ConceptFunnel_TEST=true"
|
||||
|
|
@ -122,6 +193,10 @@ export async function navigateToHeritageFunnel() {
|
|||
);
|
||||
}
|
||||
|
||||
/*
|
||||
Gets the current instance of the concept funnel cookie.
|
||||
Returns null if cookie isn't valid JSON.
|
||||
*/
|
||||
export function getConceptCookie() {
|
||||
const cookieJson = document.cookie
|
||||
?.split("; ")
|
||||
|
|
@ -135,28 +210,40 @@ export function getConceptCookie() {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Function to get the date for the saved session timeout.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
// --------- PRIVATE FUNCTIONS ---------
|
||||
async function loadOrder(referralNumber, referralDate, referralCorrelationId) {
|
||||
console.log("loading order...", referralNumber, referralDate, referralCorrelationId);
|
||||
try {
|
||||
|
||||
const response = await baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.LOAD_ORDER,
|
||||
{
|
||||
referralNumber: referralNumber.toString(),
|
||||
referralDate: referralDate,
|
||||
referralCorrelationId: referralCorrelationId
|
||||
}, false);
|
||||
|
||||
return response;
|
||||
|
||||
} catch (e) {
|
||||
console.log("error loading order:", e);
|
||||
}
|
||||
export function getDateForSavedSessionTimeout() {
|
||||
const currentDate = new Date(new Date().toUTCString())
|
||||
currentDate.setDate(currentDate.getDate() + applicationConfig.SAVED_SESSION_TIMEOUT)
|
||||
return currentDate.toUTCString();
|
||||
}
|
||||
|
||||
//-------------------------------------\\\
|
||||
// --------- PRIVATE FUNCTIONS --------- \\\
|
||||
//----------------------------------------\\\
|
||||
|
||||
/*
|
||||
Calls API to load order given the referral number, referralDate, and referralCorrelationId
|
||||
and returns the response.
|
||||
*/
|
||||
async function loadOrder(referralNumber, referralDate, referralCorrelationId) {
|
||||
console.log("loading order...", referralNumber, referralDate, referralCorrelationId);
|
||||
const response = await baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.LOAD_ORDER,
|
||||
{
|
||||
referralNumber: referralNumber.toString(),
|
||||
referralDate: referralDate,
|
||||
referralCorrelationId: referralCorrelationId
|
||||
}, false);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/*
|
||||
Used to set properties on the concept funnel cookie.
|
||||
Takes an object with properties to set. Will overwrite existing properties.
|
||||
*/
|
||||
function setConceptCookieProperties(properties) {
|
||||
if (typeof properties == "object") {
|
||||
let cookie = getConceptCookie();
|
||||
|
|
@ -173,6 +260,9 @@ function setConceptCookieProperties(properties) {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Removes concept cookie from browser.
|
||||
*/
|
||||
function deleteConceptCookie() {
|
||||
// If this cookie is ever created from the concept funnel, will need to add another
|
||||
// line with the path=/fmg/
|
||||
|
|
|
|||
|
|
@ -5,4 +5,21 @@ export function required(errorMessage) {
|
|||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
export function regex(expression, errorMessage) {
|
||||
return (value) => {
|
||||
// Field is empty, should pass
|
||||
if (!value || !value.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if email
|
||||
if (!expression.test(value)) {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
0
src/layouts/address-lookup/address-lookup.spec.js1
Normal file
0
src/layouts/address-lookup/address-lookup.spec.js1
Normal file
116
src/layouts/address-lookup/address-lookup.vue
Normal file
116
src/layouts/address-lookup/address-lookup.vue
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
<template>
|
||||
<Form
|
||||
@submit="onSubmit"
|
||||
@invalid-submit="onInvalidSubmit"
|
||||
ref="theForm"
|
||||
v-slot="{ meta }"
|
||||
autocomplete="off" >
|
||||
<div class="container-fluid shadow rounded-3 p-2 position-relative make-tall">
|
||||
<funnelHeader ref="funnelHeader" />
|
||||
<vehicleBanner ref="vehicleBanner" :displayGenericVehicleImage=false />
|
||||
<funnelSubHeader ref="funnelSubHeader" />
|
||||
<customerQuestions ref="customerQuestions" v-model="customerQuestions" />
|
||||
<funnel-footer
|
||||
ref="funnelFooter"
|
||||
:isDisabled="!meta.valid"
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
// Components
|
||||
import funnelHeader from "@/common-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
|
||||
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
|
||||
import customerQuestions from "@/layouts/address-lookup/customer-questions/customer-questions";
|
||||
import { Form } from "vee-validate";
|
||||
|
||||
// Supporting files
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import store from "@/store";
|
||||
|
||||
|
||||
export default {
|
||||
name: "address-lookup",
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
// Call APIs
|
||||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||
|
||||
// Settle promises and get results
|
||||
const promiseResultMap = [
|
||||
{
|
||||
resultKey: "cmsContent",
|
||||
promise: cmsContentPromise,
|
||||
},
|
||||
];
|
||||
|
||||
const resultMap = await settleAllPromises(promiseResultMap);
|
||||
|
||||
// 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.$refs.customerQuestions.initializeComponent([
|
||||
resultMap.cmsContent.StreetAddressQuestionWidget,
|
||||
resultMap.cmsContent.CityQuestionWidget,
|
||||
resultMap.cmsContent.StateQuestionWidget,
|
||||
resultMap.cmsContent.ZipQuestionWidget,
|
||||
resultMap.cmsContent.AlertVerificationWarningWidget,
|
||||
resultMap.cmsContent.AlertNoMatchWarningWidget,
|
||||
resultMap.cmsContent.FirstNameQuestionWidget,
|
||||
resultMap.cmsContent.LastNameQuestionWidget,
|
||||
resultMap.cmsContent.EmailAddressQuestionWidget,
|
||||
|
||||
]
|
||||
);
|
||||
});
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
customerQuestions: {
|
||||
addressQuestions: {
|
||||
streetAddress: "",
|
||||
city: "",
|
||||
state: "",
|
||||
zip: "",
|
||||
},
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
emailAddress: "",
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
return store.getters.vehicle.carId !== null;
|
||||
},
|
||||
resetDependentState() {
|
||||
// Invokes
|
||||
|
||||
},
|
||||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
customerQuestions,
|
||||
Form
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,279 @@
|
|||
<template>
|
||||
<div class="row my-4">
|
||||
<div class="col">
|
||||
<textboxQuestion v-model="addressModel.streetAddress" ref="autocomplete" inputId="autocomplete" placeholderText="Search" aria-haspopup="" hasIcon disableAutoFill validationRules="street-address-required" />
|
||||
</div>
|
||||
</div>
|
||||
<transition name="fade" mode="out-in">
|
||||
<div class="address-fields" v-show="showAddressFields" aria-live="polite">
|
||||
<div class="row my-4">
|
||||
<div class="col">
|
||||
<textboxQuestion v-model="addressModel.city" ref="city" inputId="cbf28188fdf2436688fd735915f7ee56" disableAutoFill validationRules="city-required"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-4">
|
||||
<div class="col-6">
|
||||
<dropdownQuestion v-model="addressModel.state" ref="state" inputId="8fdf9dc2e13e430eb57529499dceb3eb" :options="stateOptions" disableAutoFill validationRules="state-required" />
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<textboxQuestion v-model="addressModel.zip" ref="zip" inputId="01a9a1c2de0b4c9da8e023c9ae3be498" mask="#####" disableAutoFill validationRules="zip-required"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<alert ref="alertVerificationWarning" v-show="displayVerificationWarning"
|
||||
alertClass="alert-warning"
|
||||
:alertHeadline="alertHeadlineVerificationWarning"
|
||||
:alertCopy="alertCopyVerificationWarning"
|
||||
v-bind:isDismissible="false"
|
||||
/>
|
||||
<alert ref="alertNoMatchWarning" v-show="displayNoMatchWarning"
|
||||
alertClass="alert-warning"
|
||||
:alertHeadline="alertHeadlineNoMatchWarning"
|
||||
:alertCopy="alertCopyNoMatchWarning"
|
||||
v-bind:isDismissible="false"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import textboxQuestion from "@/common-components/textbox-question/textbox-question";
|
||||
import dropdownQuestion from "@/common-components/dropdown-question/dropdown-question";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
import { applicationConfig } from "@/constants/application-config.js";
|
||||
import { computed } from 'vue';
|
||||
import { defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
//import store from "@/store";
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule("street-address-required", required(errorMessages.STREET_ADDRESS_REQUIRED));
|
||||
defineRule("city-required", required(errorMessages.CITY_REQUIRED));
|
||||
defineRule("state-required", required(errorMessages.STATE_REQUIRED));
|
||||
defineRule("zip-required", required(errorMessages.ZIP_REQUIRED));
|
||||
|
||||
export default ({
|
||||
name: "address-questions",
|
||||
emits: ['update:modelValue'], // The component emits an event
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
streetAddress: "",
|
||||
city: "",
|
||||
state: "",
|
||||
zip: "",
|
||||
}),
|
||||
},
|
||||
validationRules: String,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
// Please do not modify, this "computed" is used to track and report
|
||||
// this object's property changes to the parent component
|
||||
const addressModel = computed({ // Use computed to wrap the object
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value),
|
||||
});
|
||||
|
||||
return {
|
||||
addressModel,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showAddressFields: false,
|
||||
displayVerificationWarning: false,
|
||||
displayNoMatchWarning: false,
|
||||
alertHeadlineVerificationWarning: "",
|
||||
alertCopyVerificationWarning: "",
|
||||
alertHeadlineNoMatchWarning: "",
|
||||
alertCopyNoMatchWarning: "",
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
stateOptions: {
|
||||
get: function () {
|
||||
return {
|
||||
'AL': 'Alabama',
|
||||
'AK': 'Alaska',
|
||||
'AZ': 'Arizona',
|
||||
'AR': 'Arkansas',
|
||||
'CA': 'California',
|
||||
'CO': 'Colorado',
|
||||
'CT': 'Connecticut',
|
||||
'DE': 'Delaware',
|
||||
'DC': 'District Of Columbia',
|
||||
'FL': 'Florida',
|
||||
'GA': 'Georgia',
|
||||
'HI': 'Hawaii',
|
||||
'ID': 'Idaho',
|
||||
'IL': 'Illinois',
|
||||
'IN': 'Indiana',
|
||||
'IA': 'Iowa',
|
||||
'KS': 'Kansas',
|
||||
'KY': 'Kentucky',
|
||||
'LA': 'Louisiana',
|
||||
'ME': 'Maine',
|
||||
'MD': 'Maryland',
|
||||
'MA': 'Massachusetts',
|
||||
'MI': 'Michigan',
|
||||
'MN': 'Minnesota',
|
||||
'MS': 'Mississippi',
|
||||
'MO': 'Missouri',
|
||||
'MT': 'Montana',
|
||||
'NE': 'Nebraska',
|
||||
'NV': 'Nevada',
|
||||
'NH': 'New Hampshire',
|
||||
'NJ': 'New Jersey',
|
||||
'NM': 'New Mexico',
|
||||
'NY': 'New York',
|
||||
'NC': 'North Carolina',
|
||||
'ND': 'North Dakota',
|
||||
'OH': 'Ohio',
|
||||
'OK': 'Oklahoma',
|
||||
'OR': 'Oregon',
|
||||
'PA': 'Pennsylvania',
|
||||
'RI': 'Rhode Island',
|
||||
'SC': 'South Carolina',
|
||||
'SD': 'South Dakota',
|
||||
'TN': 'Tennessee',
|
||||
'TX': 'Texas',
|
||||
'UT': 'Utah',
|
||||
'VT': 'Vermont',
|
||||
'VA': 'Virginia',
|
||||
'WA': 'Washington',
|
||||
'WV': 'West Virginia',
|
||||
'WI': 'Wisconsin',
|
||||
'WY': 'Wyoming',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initializeComponent(cmsContent) {
|
||||
this.$refs.autocomplete.initializeComponent(cmsContent[0].QuestionText);
|
||||
this.$refs.city.initializeComponent(cmsContent[1].QuestionText);
|
||||
this.$refs.state.initializeComponent(cmsContent[2].QuestionText);
|
||||
this.$refs.zip.initializeComponent(cmsContent[3].QuestionText);
|
||||
|
||||
// assign alert texts to this component
|
||||
this.alertHeadlineVerificationWarning = cmsContent[4].HeadlineText;
|
||||
this.alertCopyVerificationWarning = cmsContent[4].BodyText;
|
||||
|
||||
this.alertHeadlineNoMatchWarning = cmsContent[5].HeadlineText;
|
||||
this.alertCopyNoMatchWarning = cmsContent[5].BodyText;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
const addressField1 = document.getElementById("autocomplete");
|
||||
const self = this;
|
||||
|
||||
const apiKey = applicationConfig.GOOGLE_PLACES_API_KEY;
|
||||
|
||||
this.$loadScript(`https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places`)
|
||||
.then(() => {
|
||||
// Script is loaded, initialize the autocomplete textbox
|
||||
const autocomplete = new window.google.maps.places.Autocomplete(
|
||||
addressField1,
|
||||
{
|
||||
componentRestrictions: { country: ["us"] },
|
||||
fields: ["address_components"],
|
||||
types: ["address"],
|
||||
}
|
||||
);
|
||||
|
||||
// Standard place_changed event handling
|
||||
autocomplete.addListener('place_changed', fillInAddress);
|
||||
|
||||
addressField1.onblur = function() {
|
||||
const hover = document.querySelector(".pac-container .pac-item:hover");
|
||||
|
||||
// if an item has been clicked, do nothing, otherwise get first solution and use Geocoder to get the place
|
||||
if (hover === null) {
|
||||
const item = document.querySelector(".pac-container .pac-item");
|
||||
if (item != null) {
|
||||
const firstResult = item.textContent;
|
||||
const geocoder = new window.google.maps.Geocoder();
|
||||
geocoder.geocode({
|
||||
address: firstResult
|
||||
}, function (results, status) {
|
||||
if (status === window.google.maps.GeocoderStatus.OK) {
|
||||
fillInAddress(results[0]);
|
||||
self.displayVerificationWarning = true;
|
||||
self.displayNoMatchWarning = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
self.addressModel.city = "";
|
||||
self.addressModel.state = "";
|
||||
self.addressModel.zip = "";
|
||||
self.showAddressFields = true;
|
||||
self.displayVerificationWarning = false;
|
||||
self.displayNoMatchWarning = true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
function fillInAddress(place) {
|
||||
if (!place) {
|
||||
place = autocomplete.getPlace();
|
||||
}
|
||||
|
||||
if (place && place.address_components) {
|
||||
self.addressModel.streetAddress= "";
|
||||
self.showAddressFields = true;
|
||||
|
||||
for (const component of place.address_components) {
|
||||
const componentType = component.types[0];
|
||||
|
||||
switch (componentType) {
|
||||
case "street_number": {
|
||||
self.addressModel.streetAddress = component.long_name;
|
||||
break;
|
||||
}
|
||||
case "route": {
|
||||
self.addressModel.streetAddress += ' ' + component.short_name;
|
||||
break;
|
||||
}
|
||||
case "locality": {
|
||||
self.addressModel.city = component.long_name;
|
||||
break;
|
||||
}
|
||||
case "administrative_area_level_1": {
|
||||
self.addressModel.state = component.short_name;
|
||||
break;
|
||||
}
|
||||
case "postal_code": {
|
||||
self.addressModel.zip = component.long_name;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
self.displayVerificationWarning = false;
|
||||
self.displayNoMatchWarning = false;
|
||||
}
|
||||
else {
|
||||
self.displayVerificationWarning = true;
|
||||
self.displayNoMatchWarning = false;
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// Failed to fetch script
|
||||
console.log("Unable to load Google Places API script");
|
||||
});
|
||||
},
|
||||
components: {
|
||||
textboxQuestion,
|
||||
dropdownQuestion,
|
||||
alert,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
<template>
|
||||
<addressQuestions ref="addressQuestions" v-model="customerModel.addressQuestions" />
|
||||
<div class="row my-4">
|
||||
<div class="col">
|
||||
<textboxQuestion v-model="customerModel.firstName" ref="firstName" inputId="08497a2efd9a4a73a70360ab47b4838d" disableAutoFill validationRules="first-name-required" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-4">
|
||||
<div class="col">
|
||||
<textboxQuestion v-model="customerModel.lastName" ref="lastName" inputId="0030e56a57e74a4ab92de7fb8e97fec5" disableAutoFill validationRules="last-name-required" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-4">
|
||||
<div class="col">
|
||||
<textboxQuestion v-model="customerModel.emailAddress" ref="emailAddress" inputId="00450a91b8964a768ce3992e6feb890f" disableAutoFill validationRules="email-address-required|email-address-format"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import addressQuestions from "@/layouts/address-lookup/customer-questions/address-questions/address-questions";
|
||||
import textboxQuestion from "@/common-components/textbox-question/textbox-question";
|
||||
import { computed } from 'vue';
|
||||
import { defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
import { regex } from "@/helpers/validation-rules";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
//import store from "@/store";
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule("first-name-required", required(errorMessages.FIRST_NAME_REQUIRED));
|
||||
defineRule("last-name-required", required(errorMessages.LAST_NAME_REQUIRED));
|
||||
defineRule("email-address-required", required(errorMessages.EMAIL_ADDRESS_REQUIRED));
|
||||
defineRule("email-address-format", regex(/^([a-zA-Z0-9_\-.+]+)@([a-zA-Z0-9_\-.]+).([a-zA-Z]{2,})$/, errorMessages.EMAIL_ADDRESS_FORMAT));
|
||||
|
||||
//EMAIL_ADDRESS_FORMAT
|
||||
|
||||
export default ({
|
||||
name: "customer-questions",
|
||||
emits: ['update:modelValue'], // The component emits an event
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
customerQuestions: {
|
||||
addressQuestions: {
|
||||
streetAddress: "",
|
||||
city: "",
|
||||
state: "",
|
||||
zip: "",
|
||||
},
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
emailAddress: "",
|
||||
}
|
||||
}),
|
||||
},
|
||||
validationRules: String,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
// Please do not modify, this "computed" is used to track and report
|
||||
// this object's property changes to the parent component
|
||||
const customerModel = computed({ // Use computed to wrap the object
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value),
|
||||
});
|
||||
|
||||
return { customerModel };
|
||||
},
|
||||
methods: {
|
||||
initializeComponent(cmsContent){
|
||||
// pass alert texts to addressQuestions component
|
||||
this.$refs.addressQuestions.initializeComponent(cmsContent);
|
||||
|
||||
this.$refs.firstName.initializeComponent(cmsContent[6].QuestionText);
|
||||
this.$refs.lastName.initializeComponent(cmsContent[7].QuestionText);
|
||||
this.$refs.emailAddress.initializeComponent(cmsContent[8].QuestionText);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
value: {
|
||||
get: function() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set: function(newValue) {
|
||||
this.$emit("update:modelValue", newValue);
|
||||
}
|
||||
},
|
||||
},
|
||||
components: {
|
||||
addressQuestions,
|
||||
textboxQuestion,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
@ -1129,6 +1129,16 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-2">
|
||||
<div class="col">
|
||||
<alert
|
||||
alertClass="alert-success"
|
||||
alertHeadline="Multi-Paragraph Alert"
|
||||
:alertCopy="['This is an example of a MULTI-PARAGRAPH alert, which takes an array of strings instead of a single string value for alertCopy.', 'This is the second item in the array of strings.']"
|
||||
v-bind:isDismissible="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -1144,7 +1154,7 @@
|
|||
import textLink from "@/ux-components/text-link/text-link";
|
||||
import textboxQuestion from "@/common-components/textbox-question/textbox-question";
|
||||
import dropdownQuestion from "@/common-components/dropdown-question/dropdown-question";
|
||||
import vinInformation from "@/common-components/vin-information/vin-information";
|
||||
import vinInformation from "@/layouts/vin-lookup/vin-information/vin-information";
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
|
|
|
|||
|
|
@ -782,41 +782,6 @@ describe("vehicle-damage.vue", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("vehicle-damage.vue", () => {
|
||||
test("when onInvalidSubmit is triggered with errors focus will be put on the first element with an error", async () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
const mockedValidationPayload = {
|
||||
values: {},
|
||||
errors: {
|
||||
driverSideOptions: 'Please select window',
|
||||
passengerSideOptions: 'Please select window'
|
||||
},
|
||||
results: {},
|
||||
}
|
||||
const newObj = document.createElement('input');
|
||||
newObj.setAttribute("id", "testInput");
|
||||
newObj.setAttribute("data-focus-target", "driverSideOptions");
|
||||
document.body.appendChild(newObj);
|
||||
const testInputElement = document.getElementById("testInput");
|
||||
|
||||
//Act
|
||||
vehicleDamage.beforeRouteEnter.call(
|
||||
wrapper.vm,
|
||||
{ query: { fmgPage: "vehicle-damage" } },
|
||||
undefined,
|
||||
(c) => c(wrapper.vm)
|
||||
);
|
||||
wrapper.vm.onInvalidSubmit(mockedValidationPayload);
|
||||
await nextTick();
|
||||
const focusedEl = document.activeElement;
|
||||
|
||||
//Assert
|
||||
expect(testInputElement).toBe(focusedEl);
|
||||
});
|
||||
});
|
||||
|
||||
// THE FOLLOWING TEST IS NOT NECESSARILY REQUIRED FOR COVERAGE
|
||||
// BUT KEEP FOR AN EXAMPLE OF A VALIDATION TEST
|
||||
//
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
<template>
|
||||
<div class="container-fluid shadow rounded-3 p-2 position-relative make-tall">
|
||||
<Form
|
||||
@submit="onSubmit"
|
||||
@invalid-submit="onInvalidSubmit"
|
||||
ref="theForm"
|
||||
v-slot="{ meta }"
|
||||
>
|
||||
<div class="container-fluid shadow rounded-3 p-2 position-relative make-tall">
|
||||
<funnelHeader ref="funnelHeader" />
|
||||
<vehicleBanner ref="vehicleBanner" :displayGenericVehicleImage=false />
|
||||
<funnelSubHeader ref="funnelSubHeader" />
|
||||
<Form
|
||||
@submit="onSubmit"
|
||||
@invalid-submit="onInvalidSubmit"
|
||||
ref="theForm"
|
||||
v-slot="{ meta }"
|
||||
class="vehicle-damage-form"
|
||||
>
|
||||
<damageLocationQuestion
|
||||
ref="damageLocation"
|
||||
v-model="selectedDamageLocations"
|
||||
|
|
@ -27,7 +26,7 @@
|
|||
v-show="hasRepairReplaceConflict"
|
||||
alertClass="alert-danger"
|
||||
alertHeadline="You'll need to schedule separate appointments"
|
||||
alertCopy="Vehicle service requiring both glass repair and replacement must be scheduled separately, as they're performed by different technicians. Continue scheduling your first service now, and then come back to schedule the second service."
|
||||
:alertCopy="['Vehicle service requiring both glass repair and replacement must be scheduled separately, as they\'re performed by different technicians.', 'Continue scheduling your first service now, and then come back to schedule the second service.']"
|
||||
:isDismissible="false"
|
||||
/>
|
||||
<sideDoorOptions
|
||||
|
|
@ -50,8 +49,8 @@
|
|||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -166,19 +165,6 @@ export default {
|
|||
this.$route
|
||||
);
|
||||
},
|
||||
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
|
||||
const errorNames = errors ? Object.keys(errors) : [];
|
||||
const firstErrorEl = errorNames[0];
|
||||
if (firstErrorEl) {
|
||||
const qsString = "[data-focus-target='" + firstErrorEl + "']";
|
||||
const el = document.querySelector(qsString);
|
||||
el && el.focus();
|
||||
}
|
||||
},
|
||||
|
||||
getDamageLocationsFromStore() {
|
||||
var glassSelections = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
v-show="showNoReplacementAvailableError"
|
||||
alertClass="alert-danger"
|
||||
alertHeadline="Service not available"
|
||||
alertCopy="We're sorry, but we currently offer only repair service for your vehicle type. Need help with next steps? Call us at 800-394-0288."
|
||||
:alertCopy="['We\'re sorry, but we currently offer only repair service for your vehicle type.', 'Need help with next steps? Call us at 800-394-0288.']"
|
||||
:isDismissible="false"
|
||||
/>
|
||||
<windshieldChipCountQuestion ref="windshieldChipCountQuestion"
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
isMultiSelect
|
||||
groupName="WindshieldReplaceOptions"
|
||||
v-model="selectedWindshieldReplaceOptionsValues"
|
||||
validationRules="windshield-replace-options-required|preventSplitAndSingleTogether"
|
||||
validationRules="windshield-replace-options-required|prevent-split-and-single-together"
|
||||
:suppressError="hasSplitSingleConflict"
|
||||
/>
|
||||
<alert
|
||||
|
|
@ -56,7 +56,7 @@ defineRule("windshield-damage-type-required", required(errorMessages.WINDSHIELD_
|
|||
defineRule("windshield-chip-count-required", required(errorMessages.WINDSHIELD_CHIP_COUNT_REQUIRED));
|
||||
defineRule("windshield-replace-options-required", required(errorMessages.WINSHIELD_REPLACE_OPTIONS_REQUIRED));
|
||||
|
||||
defineRule("checkForRepairAndReplace", (value, [otherFieldValue]) => {
|
||||
defineRule("check-for-repair-and-replace", (value, [otherFieldValue]) => {
|
||||
if (value.toString().toUpperCase().includes(damageLocationsSelected.REPAIR.toUpperCase()) &&
|
||||
otherFieldValue.toString().toUpperCase().includes(damageLocationsSelected.WINDSHIELD.toUpperCase()) &&
|
||||
Array.isArray(otherFieldValue) &&
|
||||
|
|
@ -66,13 +66,13 @@ defineRule("checkForRepairAndReplace", (value, [otherFieldValue]) => {
|
|||
}
|
||||
return true;
|
||||
});
|
||||
defineRule("repairOnly", (value) => {
|
||||
defineRule("repair-only", (value) => {
|
||||
if (value.toString().toUpperCase() === damageLocationsSelected.REPAIR.toUpperCase()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
defineRule("preventSplitAndSingleTogether", (value) => {
|
||||
defineRule("prevent-split-and-single-together", (value) => {
|
||||
if (value.toString().toUpperCase().includes(damageLocationsSelected.SINGLE.toUpperCase()) &&
|
||||
(value.toString().toUpperCase().includes(damageLocationsSelected.DRIVER.toUpperCase()) ||
|
||||
value.toString().toUpperCase().includes(damageLocationsSelected.PASSENGER.toUpperCase())))
|
||||
|
|
@ -184,10 +184,10 @@ export default ({
|
|||
},
|
||||
windshieldDamageTypeQuestionValidationRules() {
|
||||
// Note: the validation rules string is not dynamic (it cannot be changed once component has been created)
|
||||
let validationRules = "windshield-damage-type-required|checkForRepairAndReplace:@DamageLocationQuestion";
|
||||
let validationRules = "windshield-damage-type-required|check-for-repair-and-replace:@DamageLocationQuestion";
|
||||
// if vehicle has no windshield replacement option
|
||||
if (!this.isWindshieldReplaceAvailable) {
|
||||
validationRules = validationRules.concat('|repairOnly');
|
||||
validationRules = validationRules.concat('|repair-only');
|
||||
}
|
||||
return validationRules;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import vinInformation from "@/common-components/vin-information/vin-information";
|
||||
import vinInformation from "@/layouts/vin-lookup/vin-information/vin-information";
|
||||
|
||||
describe("vinInformation.vue", () => {
|
||||
it("Should return input class active if isActive is true", async () => {
|
||||
|
|
@ -1 +1,167 @@
|
|||
test.todo("some test to be written in the future");
|
||||
// Components
|
||||
import vinLookup from "@/layouts/vin-lookup/vin-lookup.vue";
|
||||
import vehicleDamage from "@/layouts/vehicle-damage/vehicle-damage.vue";
|
||||
import funnelHeader from "@/common-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
|
||||
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
|
||||
import sideDoorOptions from "@/layouts/vehicle-damage/side-door-options/side-door-options";
|
||||
import damageLocationQuestion from "@/layouts/vehicle-damage/damage-location-question/damage-location-question";
|
||||
import windshieldOptions from "@/layouts/vehicle-damage/windshield-options/windshield-options";
|
||||
import replaceOptionsQuestion from "@/layouts/vehicle-damage/replace-options-question/replace-options-question";
|
||||
|
||||
// Supporting Files
|
||||
import { settleAllPromises } from "@/helpers/layout-helper.js";
|
||||
import baseMixin from "@/mixins/base-mixin";
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { mount, flushPromises } from "@vue/test-utils";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import { nextTick } from "vue";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import { storeMutations } from "@/constants/store-mutations";
|
||||
import store from "@/store";
|
||||
import { validate } from "vee-validate";
|
||||
import { damageLocationsSelected } from "@/constants/damage-locations-selected.js";
|
||||
|
||||
// Mock our module for promises.
|
||||
jest.mock("@/helpers/layout-helper.js", () => ({
|
||||
settleAllPromises: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock fetchCmsContentForPage
|
||||
jest.mock("@/helpers/cms-content-helper", () => ({
|
||||
fetchCmsContentForPage: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock Store
|
||||
jest.mock("@/store", () => ({
|
||||
commit: jest.fn(),
|
||||
dispatch: jest.fn(),
|
||||
getters: {
|
||||
vehicle: {
|
||||
carId: "C00000000",
|
||||
image: "test.jpg",
|
||||
},
|
||||
eventBusItem: jest.fn(),
|
||||
damage: {
|
||||
glassToReplace: []
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe("vin-lookup.vue", () => {
|
||||
|
||||
test("Call resetDependentState", async() => {
|
||||
const {wrapper} = setupMocks({});
|
||||
wrapper.vm.resetDependentState();
|
||||
expect(store.dispatch).toBeCalledWith(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// TEMP
|
||||
function setupMocks({
|
||||
pageHeaderWidgetHeaderText = {},
|
||||
mountOptionsMockData = {
|
||||
router: {
|
||||
navigate: jest.fn(),
|
||||
},
|
||||
store: {
|
||||
getters: {
|
||||
vehicle: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
}) {
|
||||
//Mock api responses
|
||||
baseMixin.methods.dispatchNonBlockingStoreAction = jest.fn();
|
||||
const apiResponses = {
|
||||
cmsContent: {
|
||||
FunnelSubHeaderWidget: pageHeaderWidgetHeaderText,
|
||||
VehicleBannerWidget: {
|
||||
GenericVehicleImage:
|
||||
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
|
||||
},
|
||||
FunnelHeaderWidget: {
|
||||
LogoImage:
|
||||
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
|
||||
},
|
||||
},
|
||||
damageOptions: {
|
||||
driverSideOptions: {
|
||||
availableReplacementOptions: ["Front", "Back", "Side"],
|
||||
},
|
||||
passengerSideOptions: {
|
||||
availableReplacementOptions: ["Front", "Back", "Side"],
|
||||
},
|
||||
windshieldOptions: {
|
||||
availableReplacementOptions: ["Single", "Driver", "Passenger"],
|
||||
},
|
||||
backGlassOptions: {
|
||||
availableReplacementOptions: ["Front", "Back", "Side"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const apiPromise = Promise.resolve(apiResponses);
|
||||
|
||||
settleAllPromises.mockImplementation(() => apiPromise);
|
||||
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
|
||||
|
||||
//Mock damage initialize methods
|
||||
funnelHeader.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
};
|
||||
|
||||
vehicleBanner.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
};
|
||||
|
||||
funnelSubHeader.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
};
|
||||
|
||||
damageLocationQuestion.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
};
|
||||
|
||||
sideDoorOptions.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
};
|
||||
|
||||
windshieldOptions.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
};
|
||||
|
||||
replaceOptionsQuestion.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
updateSelectedValues: jest.fn(),
|
||||
};
|
||||
|
||||
funnelFooter.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
}
|
||||
|
||||
const mountOptions = getMountOptions(mountOptionsMockData);
|
||||
mountOptions['attachTo'] = document.body; // append wrapper to document.body to test DOM methods
|
||||
|
||||
const wrapper = mount(vinLookup, mountOptions);
|
||||
|
||||
const funnelHeaderWrapper = wrapper.findComponent({ name: "funnelHeader" });
|
||||
funnelHeaderWrapper.vm.initializeComponent =
|
||||
funnelHeader.methods.initializeComponent;
|
||||
|
||||
const vehicleBannerWrapper = wrapper.findComponent({ name: "vehicleBanner" });
|
||||
vehicleBannerWrapper.vm.initializeComponent =
|
||||
vehicleBanner.methods.initializeComponent;
|
||||
|
||||
const funnelSubHeaderWrapper = wrapper.findComponent({ name: "funnelSubHeader" });
|
||||
funnelSubHeaderWrapper.vm.initializeComponent =
|
||||
funnelSubHeader.methods.initializeComponent;
|
||||
|
||||
const funnelFooterWrapper = wrapper.findComponent({ name: "funnelFooter" });
|
||||
funnelFooterWrapper.vm.initializeComponent =
|
||||
funnelFooter.methods.initializeComponent;
|
||||
|
||||
return { wrapper, apiPromise };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,17 +5,17 @@
|
|||
<funnelSubHeader ref="funnelSubHeader" />
|
||||
<h1>VIN Lookup Placeholder Page</h1>
|
||||
<Form
|
||||
@submit="onSubmit"
|
||||
@invalid-submit="onInvalidSubmit"
|
||||
ref="theForm"
|
||||
v-slot="{ meta }"
|
||||
class="d-flex flex-column h-100"
|
||||
@submit="onSubmit"
|
||||
@invalid-submit="onInvalidSubmit"
|
||||
ref="theForm"
|
||||
v-slot="{ meta }"
|
||||
class="d-flex flex-column h-100"
|
||||
>
|
||||
<funnel-footer
|
||||
ref="funnelFooter"
|
||||
:isDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
ref="funnelFooter"
|
||||
:isDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
|
|
@ -33,6 +33,7 @@ import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
|||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import store from "@/store";
|
||||
import { storeMutations } from "@/constants/store-mutations";
|
||||
import baseMixin from "@/mixins/base-mixin";
|
||||
import { Form, defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
|
|
@ -86,6 +87,16 @@ export default {
|
|||
arePagePrerequisitesValid() {
|
||||
return true;
|
||||
},
|
||||
resetDependentState() {
|
||||
store.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
|
||||
},
|
||||
backButtonAction() {
|
||||
// route to move backwards
|
||||
this.$router.navigate(
|
||||
this.navigationScenarios.CLICKED_BACK,
|
||||
this.$route
|
||||
);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { createApp } from "vue";
|
||||
import LoadScript from "vue-plugin-load-script";
|
||||
import Maska from "maska";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import store from "@/store";
|
||||
|
|
@ -12,6 +13,7 @@ const vueApp = createApp(App);
|
|||
vueApp.use(router);
|
||||
vueApp.use(store);
|
||||
vueApp.use(LoadScript);
|
||||
vueApp.use(Maska);
|
||||
vueApp.mixin(baseMixin);
|
||||
|
||||
vueApp.mount("#app");
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import store from "@/store";
|
||||
import router from "@/router";
|
||||
import { storeActions } from "@/constants/store-actions.js";
|
||||
import { storeMutations } from "@/constants/store-mutations.js";
|
||||
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
|
||||
import { widgetNames } from "@/constants/widget-names.js";
|
||||
import { vehicleCategories } from "@/constants/vehicle-categories.js";
|
||||
|
||||
export default {
|
||||
|
|
@ -22,6 +20,18 @@ export default {
|
|||
savePageDataToStore(page, data){
|
||||
store.commit(storeMutations.UPDATE_PAGE_DATA, { page: page, data: data });
|
||||
},
|
||||
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
|
||||
const errorNames = errors ? Object.keys(errors) : [];
|
||||
const firstErrorEl = errorNames[0];
|
||||
if (firstErrorEl) {
|
||||
const qsString = "[data-focus-target='" + firstErrorEl + "']";
|
||||
const el = document.querySelector(qsString);
|
||||
el && el.focus();
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
storeActions() {
|
||||
|
|
@ -33,9 +43,6 @@ export default {
|
|||
navigationScenarios() {
|
||||
return navigationScenarios;
|
||||
},
|
||||
widgetNames() {
|
||||
return widgetNames;
|
||||
},
|
||||
vehicleCategories() {
|
||||
return vehicleCategories;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import baseMixin from "@/mixins/base-mixin";
|
||||
import { storeActions } from "@/constants/store-actions.js";
|
||||
import { widgetNames } from "@/constants/widget-names.js";
|
||||
import { storeMutations } from "@/constants/store-mutations.js";
|
||||
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
|
||||
import { vehicleCategories } from "@/constants/vehicle-categories.js";
|
||||
import store from "@/store";
|
||||
|
||||
describe("baseMixin.js", () => {
|
||||
|
|
@ -66,16 +66,34 @@ describe("baseMixin.js", () => {
|
|||
// Assert
|
||||
expect(navigationScenariosForTest).toEqual(navigationScenarios);
|
||||
});
|
||||
|
||||
test("computed: widgetNames should be equal to import object", () => {
|
||||
test("computed: vehicleCategories should be equal to import object", () => {
|
||||
// Arrange
|
||||
const mixIn = getMixInInstance({});
|
||||
|
||||
// Act
|
||||
let widgetNamesForTest = mixIn.computed.widgetNames();
|
||||
let vehicleCategoriesForTest = mixIn.computed.vehicleCategories();
|
||||
|
||||
// Assert
|
||||
expect(widgetNamesForTest).toEqual(widgetNames);
|
||||
expect(vehicleCategoriesForTest).toEqual(vehicleCategories);
|
||||
});
|
||||
|
||||
test("onInvalidSubmit: puts focus on first error", () => {
|
||||
// Arrange
|
||||
const mixIn = getMixInInstance({});
|
||||
const validationData = {
|
||||
errors: {
|
||||
fieldOne: 'error message 1',
|
||||
fieldTwo: 'error message 2',
|
||||
}
|
||||
};
|
||||
|
||||
global.document.querySelector = jest.fn();
|
||||
|
||||
// Act
|
||||
mixIn.methods.onInvalidSubmit(validationData);
|
||||
|
||||
// Assert
|
||||
expect(global.document.querySelector).toBeCalledWith("[data-focus-target='fieldOne']");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -100,7 +118,7 @@ function getMixInInstance({ isDispatchSuccess = true }) {
|
|||
const baseMixIn = baseMixin;
|
||||
baseMixIn.methods.$route = route;
|
||||
baseMixIn.methods.storeActions = storeActions;
|
||||
baseMixIn.methods.widgetNames = widgetNames;
|
||||
baseMixIn.methods.vehicleCategories = vehicleCategories;
|
||||
|
||||
store.dispatch = storeDispatch;
|
||||
store.commit = jest.fn();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ 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 * as integrationHelper from "@/helpers/heritage-integration-helper";
|
||||
import * as heritageIntegrationHelper from "@/helpers/heritage-integration-helper";
|
||||
|
||||
import baseMixin from "@/mixins/base-mixin";
|
||||
import eventBus from "@/helpers/event-bus/event-bus";
|
||||
|
|
@ -35,20 +35,25 @@ const routes = [
|
|||
} else {
|
||||
try {
|
||||
|
||||
// Check our 'Session' is still good. If not, reset state and go back to the start.
|
||||
if (!integrationHelper.isConceptSessionStillActive()) {
|
||||
baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.RESET_STATE);
|
||||
// If the saved session has timed out, clear the session, execute 404 logic.
|
||||
if (!heritageIntegrationHelper.isSavedSessionStillActive()) {
|
||||
await GoToFunnelStartOn404(next);
|
||||
}
|
||||
|
||||
// Create funnel cookie, or update it if it already exists.
|
||||
integrationHelper.updateOrCreateConceptCookie();
|
||||
// Process concept funnel cookie.
|
||||
heritageIntegrationHelper.updateOrCreateConceptCookie();
|
||||
|
||||
// On entering the concept funnel "fresh", read cookie information, decide what to do next.
|
||||
if (from.redirectedFrom === undefined) {
|
||||
await integrationHelper.loadOrderIfPresent();
|
||||
const loadOrderResponse = await heritageIntegrationHelper.loadOrderIfPresent();
|
||||
const pageToRedirectTo = await heritageIntegrationHelper.getPageToRouteExistingOrderTo(to, loadOrderResponse);
|
||||
|
||||
const pageToRedirectTo = await integrationHelper.getPageToRouteExistingOrderTo();
|
||||
// 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 heritageIntegrationHelper.navigateToHeritageFunnel();
|
||||
return next(false);
|
||||
}
|
||||
|
||||
// Assign our fmgPage so it will load normally like the other pages.
|
||||
to.query.fmgPage = pageToRedirectTo;
|
||||
|
|
@ -154,8 +159,8 @@ async function navigate(scenario, currentRoute, invalidateOnSave, optionalQuery
|
|||
baseMixin.methods.savePageDataToStore(destinationFmgPageValue, optionalPageData);
|
||||
|
||||
// if cookie and referralNumber/Date exists
|
||||
if (integrationHelper.getConceptCookie()?.ReferralNumber && integrationHelper.getConceptCookie()?.ReferralDate) {
|
||||
await integrationHelper.saveOrder();
|
||||
if (heritageIntegrationHelper.getConceptCookie()?.ReferralNumber && heritageIntegrationHelper.getConceptCookie()?.ReferralDate) {
|
||||
await heritageIntegrationHelper.saveOrder();
|
||||
}
|
||||
|
||||
router.push({
|
||||
|
|
@ -187,7 +192,7 @@ function getNavigationMap(scenario, currentRoute) {
|
|||
//---------------------------------------------------------- Private Functions ----------------------------------------------------------
|
||||
|
||||
// Navigate to an external url.
|
||||
function navigateToUrl(url, optionalQuery) {
|
||||
function navigateToUrl(url, optionalQuery = {}) {
|
||||
// possibly show some loading screen in the future here.
|
||||
var externalUrl = new URL(url);
|
||||
|
||||
|
|
|
|||
5
src/router/router-constants/externalUrl-values.js
Normal file
5
src/router/router-constants/externalUrl-values.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
const externalUrls = {
|
||||
HERITAGE_FUNNEL: process.env.VUE_APP_HERITAGE_FUNNEL,
|
||||
};
|
||||
|
||||
export { externalUrls };
|
||||
|
|
@ -4,6 +4,7 @@ const fmgPageValues = {
|
|||
VEHICLE_MODEL: "vehicle-model",
|
||||
VEHICLE_STYLE: "vehicle-style",
|
||||
VEHICLE_DAMAGE: "vehicle-damage",
|
||||
ADDRESS_LOOKUP: "address-lookup",
|
||||
VIN_LOOKUP: "vin-lookup",
|
||||
VEHICLE_PARTS: "vehicle-parts",
|
||||
PART_QUESTIONS: "part-questions",
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ const navigationScenarios = {
|
|||
SELECTED_PARTS: "SELECTED_PARTS",
|
||||
SELECTED_DAMAGE_WITH_SINGLE_PART: "SELECTED_DAMAGE_WITH_SINGLE_PART",
|
||||
SELECTED_DAMAGE_WITH_MULTIPLE_PARTS: "SELECTED_DAMAGE_WITH_MULTIPLE_PARTS",
|
||||
SELECTED_DAMAGE_WITH_PART_QUESTIONS: "SELECTED_DAMAGE_WITH_PART_QUESTIONS",
|
||||
MOVE_TO_HERITAGE_FUNNEL: "MOVE_TO_HERITAGE_FUNNEL",
|
||||
SELECTED_DAMAGE_WITH_PART_QUESTIONS: "SELECTED_DAMAGE_WITH_PART_QUESTIONS"
|
||||
};
|
||||
|
||||
export { navigationScenarios };
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
const routerParameterKeys = { };
|
||||
|
||||
export { routerParameterKeys };
|
||||
|
|
@ -102,6 +102,19 @@ const routingTable = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fmgPageValue: fmgPageValues.VIN_LOOKUP,
|
||||
maps: [
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_BACK,
|
||||
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.VIN_LOOKUP,
|
||||
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export { routingTable };
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { createStore } from "vuex";
|
||||
import { endpoints } from "@/constants/endpoints.js";
|
||||
import { storeMutations } from "@/constants/store-mutations";
|
||||
import { getDateForSavedSessionTimeout } from "@/helpers/heritage-integration-helper";
|
||||
import createPersistedState from "vuex-persistedstate";
|
||||
import globalMethods from "@/global-methods";
|
||||
|
||||
|
|
@ -17,16 +18,32 @@ const getDefaultState = () => {
|
|||
style: null,
|
||||
carId: null,
|
||||
category: null,
|
||||
vin: null,
|
||||
imageUrl: null,
|
||||
imageVifNumber: null,
|
||||
imageColor: null,
|
||||
registration: {
|
||||
licensePlate: null,
|
||||
address: null,
|
||||
city: null,
|
||||
state: null,
|
||||
zipCode: null,
|
||||
firstName: null,
|
||||
lastName: null,
|
||||
},
|
||||
},
|
||||
serviceLocation: {
|
||||
zip: null,
|
||||
},
|
||||
customer: {
|
||||
emailAddress: null,
|
||||
},
|
||||
damage: {
|
||||
isRepair: null,
|
||||
numberOfChips: null,
|
||||
glassToReplace: null,
|
||||
},
|
||||
lineItems:{
|
||||
lineItems: {
|
||||
glassParts: null,
|
||||
otherParts: null
|
||||
},
|
||||
|
|
@ -37,7 +54,8 @@ const getDefaultState = () => {
|
|||
},
|
||||
applicationUser: {
|
||||
eventBus: [],
|
||||
pageData: {}
|
||||
pageData: {},
|
||||
savedSessionTimeout: getDateForSavedSessionTimeout()
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
@ -74,19 +92,19 @@ export const mutations = {
|
|||
updateVehicleImageColor(state, imageColor) {
|
||||
state.order.vehicle.imageColor = imageColor;
|
||||
},
|
||||
updateIsRepair(state, isRepair){
|
||||
updateIsRepair(state, isRepair) {
|
||||
state.order.damage.isRepair = isRepair;
|
||||
},
|
||||
updateNumberOfChips(state, numberOfChips){
|
||||
updateNumberOfChips(state, numberOfChips) {
|
||||
state.order.damage.numberOfChips = numberOfChips;
|
||||
},
|
||||
updateGlassToReplace(state, glassToReplace){
|
||||
updateGlassToReplace(state, glassToReplace) {
|
||||
state.order.damage.glassToReplace = glassToReplace;
|
||||
},
|
||||
updateParts(state, partsData){
|
||||
updateParts(state, partsData) {
|
||||
state.order.lineItems.glassParts = partsData;
|
||||
},
|
||||
updatePageData(state, pageData){
|
||||
updatePageData(state, pageData) {
|
||||
state.applicationUser.pageData[pageData.page] = pageData.data;
|
||||
},
|
||||
updateReferralCorrelationId(state, referralCorrelationId) {
|
||||
|
|
@ -136,7 +154,13 @@ export const mutations = {
|
|||
state.order.damage.glassToReplace = null;
|
||||
},
|
||||
resetRegistrationState(state) {
|
||||
|
||||
state.order.vehicle.registration.licensePlate = null;
|
||||
state.order.vehicle.registration.address = null;
|
||||
state.order.vehicle.registration.city = null;
|
||||
state.order.vehicle.registration.state = null;
|
||||
state.order.vehicle.registration.zipCode = null;
|
||||
state.order.vehicle.registration.firstName = null;
|
||||
state.order.vehicle.registration.lastName = null;
|
||||
},
|
||||
resetPartsState(state) {
|
||||
state.order.lineItems.glassParts = null;
|
||||
|
|
@ -186,7 +210,9 @@ export const getters = {
|
|||
lineItems: (state) => state.order.lineItems,
|
||||
pageData: (state) => (page) => {
|
||||
return state.applicationUser.pageData[page];
|
||||
}
|
||||
},
|
||||
applicationUser: (state) => state.applicationUser,
|
||||
order: (state) => state.order,
|
||||
}
|
||||
|
||||
// Export Actions
|
||||
|
|
@ -366,7 +392,7 @@ export const actions = {
|
|||
referralDate: referralDate,
|
||||
referralCorrelationId: referralCorrelationId
|
||||
},
|
||||
}).then( (response) => {
|
||||
}).then((response) => {
|
||||
context.commit(storeMutations.SET_LOAD_CONCEPT_SESSION_INFO, response.data);
|
||||
return response;
|
||||
});
|
||||
|
|
@ -384,4 +410,6 @@ export default createStore({
|
|||
mutations,
|
||||
getters,
|
||||
actions,
|
||||
});
|
||||
});
|
||||
|
||||
// Private Functions
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
.has-error {
|
||||
&.list-button,
|
||||
&.list-card {
|
||||
border: 1px solid $gray-500;
|
||||
border: 1px solid $red;
|
||||
color: $red;
|
||||
label {
|
||||
box-shadow: 0 0 1px $red;
|
||||
box-shadow: 0 0 1px $red !important;
|
||||
border-radius: .5rem;
|
||||
}
|
||||
input[type=checkbox]:focus + label,
|
||||
input[type=radio]:focus + label {
|
||||
box-shadow: 0 0 0 2.5px $red;
|
||||
}
|
||||
}
|
||||
input[type=checkbox]:focus + label,
|
||||
input[type=radio]:focus + label,
|
||||
|
||||
input[type=checkbox]:checked + label,
|
||||
input[type=radio]:checked + label {
|
||||
box-shadow: 0 0 0 2.5px transparent !important;
|
||||
|
|
@ -19,6 +22,10 @@
|
|||
label {
|
||||
border: 1px solid $red;
|
||||
}
|
||||
input[type=checkbox]:focus + label,
|
||||
input[type=radio]:focus + label {
|
||||
box-shadow: 0 0 1px $red !important;
|
||||
}
|
||||
}
|
||||
&.ui-radio,
|
||||
&.ui-checkbox {
|
||||
|
|
@ -69,9 +76,9 @@
|
|||
&.btn.btn-primary:hover,
|
||||
&.btn.btn-primary:focus,
|
||||
&.btn.btn-primary:focus-visible {
|
||||
color: $gray;
|
||||
background: $gray-200;
|
||||
box-shadow: none;
|
||||
color: $gray !important;
|
||||
background: $gray-200 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,30 @@
|
|||
test.todo("some test to be written in the future");
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import alert from "./alert";
|
||||
|
||||
describe("alert.vue", () => {
|
||||
|
||||
it("Should set isMultiParagraph to true if alertCopy is an array of strings", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(alert, {
|
||||
propsData: {
|
||||
alertCopy: ["one", "two"]
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.isMultiParagraph).toBe(true);
|
||||
});
|
||||
|
||||
it("Should set isMultiParagraph to false if alertCopy is a single string", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(alert, {
|
||||
propsData: {
|
||||
alertCopy: "three"
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.isMultiParagraph).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
<template>
|
||||
<div
|
||||
class="alert fade show text-center mb-0 py-2 px-3"
|
||||
class="alert fade show text-center mb-0 py-2 px-4"
|
||||
role="alert"
|
||||
:class="[isDismissible ? 'alert-dismissible' : '', this.alertClass]"
|
||||
>
|
||||
<p class="m-0 fw-bold small alert-heading">{{ alertHeadline }}</p>
|
||||
<p class="m-0 text-body small">{{ alertCopy }}</p>
|
||||
<span v-if="isMultiParagraph">
|
||||
<p class="m-1 text-body small" v-for="(para, index) in alertCopy" :key="index">
|
||||
{{ para }}
|
||||
</p>
|
||||
</span>
|
||||
<p class="m-0 text-body small" v-else>{{ alertCopy }}</p>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close p-2"
|
||||
|
|
@ -30,7 +35,7 @@ export default {
|
|||
name: "alert",
|
||||
props: {
|
||||
alertHeadline: String,
|
||||
alertCopy: String,
|
||||
alertCopy: [Array, String],
|
||||
isDismissible: Boolean,
|
||||
/*
|
||||
alertClass class names:
|
||||
|
|
@ -41,6 +46,14 @@ export default {
|
|||
*/
|
||||
alertClass: String,
|
||||
},
|
||||
computed: {
|
||||
isMultiParagraph() {
|
||||
if (typeof this.alertCopy == "string") {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -55,12 +55,8 @@ export default {
|
|||
color: $white;
|
||||
justify-content: center;
|
||||
font-weight: 500;
|
||||
&:hover {
|
||||
background: linear-gradient(
|
||||
270deg,
|
||||
rgba(6, 87, 124, 1) 0%,
|
||||
rgba(6, 87, 124, 1) 100%
|
||||
);
|
||||
@media (hover: hover) {
|
||||
background: linear-gradient(270deg, $blue-500 0%, $blue-700 100%);
|
||||
}
|
||||
&:focus, // Mouse, touch, stylus focus
|
||||
&:focus-visible {
|
||||
|
|
|
|||
|
|
@ -109,11 +109,15 @@ export default {
|
|||
const fieldOptions = {
|
||||
type: inputType,
|
||||
checkedValue: props.value,
|
||||
potentialInitialValue: props.selectedValues,
|
||||
};
|
||||
|
||||
if (Array.isArray(props.selectedValues) && props.selectedValues.length == 1) {
|
||||
fieldOptions['initialValue'] = fieldOptions.checkedValue;
|
||||
// Set initialValue for validation setup if pre-selected
|
||||
// NOTE: props.selectedValues could be an array of strings, or an array of integers...
|
||||
if (props.selectedValues && (props.selectedValues.includes(props.value) || props.selectedValues.includes(parseInt(props.value)))) {
|
||||
fieldOptions['initialValue'] = fieldOptions.potentialInitialValue;
|
||||
}
|
||||
|
||||
const {
|
||||
checked,
|
||||
handleChange,
|
||||
|
|
@ -124,6 +128,7 @@ export default {
|
|||
checked,
|
||||
handleChange,
|
||||
errors,
|
||||
fieldOptions, // only need to expose this for unit test purposes
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -166,11 +166,15 @@ describe("list-button.vue", () => {
|
|||
isRequired: true,
|
||||
isWide: false,
|
||||
modelValue: ["List Card Checkbox"],
|
||||
buttonID: 'list-card-id'
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.vm.handleCheckChange();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{value: "List Card Checkbox", checkValue: Boolean}]);
|
||||
expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{value: "List Card Checkbox", checkValue: Boolean, buttonId: 'list-card-id'}]);
|
||||
|
||||
});
|
||||
|
||||
it("Should set checkValue data if selectedButtonIDs has value(s)", async () => {
|
||||
|
|
@ -192,4 +196,5 @@ describe("list-button.vue", () => {
|
|||
// Assert
|
||||
expect(wrapper.componentVM.checkValue).toEqual("Car-Front");
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -100,7 +100,13 @@ export default {
|
|||
handleCheckChange(newValue, oldValue){
|
||||
const isInitialization = typeof(oldValue) === 'function';
|
||||
if (!isInitialization) {
|
||||
this.$emit('isCheckedChanged', { checkValue: this.checkValue, value: this.value.toString() });
|
||||
const emitEvent = {
|
||||
checkValue: this.checkValue,
|
||||
value: this.value.toString(),
|
||||
buttonId: this.buttonID.toString(),
|
||||
};
|
||||
this.$emit('isCheckedChanged', emitEvent);
|
||||
this.$emit("update:modelValue", emitEvent);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -109,14 +115,19 @@ export default {
|
|||
},
|
||||
setup(props) {
|
||||
const inputType = props.isMultiSelect ? "checkbox" : "radio";
|
||||
|
||||
const fieldOptions = {
|
||||
type: inputType,
|
||||
checkedValue: props.value,
|
||||
potentialInitialValue: props.selectedValues,
|
||||
};
|
||||
|
||||
if (Array.isArray(props.selectedValues) && props.selectedValues.length == 1) {
|
||||
fieldOptions['initialValue'] = fieldOptions.checkedValue;
|
||||
// Set initialValue for validation setup if pre-selected
|
||||
// NOTE: props.selectedValues could be an array of strings, or an array of integers...
|
||||
if (props.selectedValues && (props.selectedValues.includes(props.value) || props.selectedValues.includes(parseInt(props.value)))) {
|
||||
fieldOptions['initialValue'] = fieldOptions.potentialInitialValue;
|
||||
}
|
||||
|
||||
const {
|
||||
checked,
|
||||
handleChange,
|
||||
|
|
@ -127,6 +138,7 @@ export default {
|
|||
checked,
|
||||
handleChange,
|
||||
errors,
|
||||
fieldOptions, // only need to expose this for unit test purposes
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -232,5 +232,20 @@ describe("list-card.vue", () => {
|
|||
expect(wrapper.componentVM.checkValue).toEqual("Car-Front");
|
||||
});
|
||||
|
||||
it("Should set an initial value for validation if selectedValues include the value", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(listCard, {
|
||||
propsData: {
|
||||
value: "Windshield",
|
||||
groupName: "radio 1",
|
||||
modelValue: ["Windshield"],
|
||||
selectedValues: ["Windshield"],
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.fieldOptions.initialValue).toEqual([ 'Windshield' ]);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
|
||||
<script>
|
||||
import { useField } from "vee-validate";
|
||||
|
||||
export default {
|
||||
name: "listCard",
|
||||
props: {
|
||||
|
|
@ -135,11 +136,14 @@ export default {
|
|||
|
||||
const fieldOptions = {
|
||||
type: inputType,
|
||||
checkedValue: props.value,
|
||||
checkedValue: props.value, // EX: "Single" or "Passenger"
|
||||
potentialInitialValue: props.selectedValues,
|
||||
};
|
||||
|
||||
if (Array.isArray(props.selectedValues) && props.selectedValues.length == 1) {
|
||||
fieldOptions['initialValue'] = fieldOptions.checkedValue;
|
||||
// Set initialValue for validation setup if pre-selected
|
||||
// NOTE: props.selectedValues could be an array of strings, or an array of integers...
|
||||
if (props.selectedValues && (props.selectedValues.includes(props.value) || props.selectedValues.includes(parseInt(props.value)))) {
|
||||
fieldOptions['initialValue'] = fieldOptions.potentialInitialValue;
|
||||
}
|
||||
|
||||
const {
|
||||
|
|
@ -150,6 +154,7 @@ export default {
|
|||
return {
|
||||
handleChange,
|
||||
errors,
|
||||
fieldOptions, // only need to expose this for unit test purposes
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
@ -204,17 +209,23 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
&:focus + label {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&:checked + label {
|
||||
background: $blue-100;
|
||||
box-shadow: 0 0 0 1px $blue;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&:focus + label {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&:checked {
|
||||
&:focus + label {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
}
|
||||
|
||||
&:checked + label {
|
||||
p {
|
||||
color: $black;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ process.env.VUE_APP_CONSUMER_API_GATEWAY =
|
|||
process.env.VUE_APP_HERITAGE_FUNNEL =
|
||||
"http://localhost:38000/default.aspx";
|
||||
|
||||
process.env.VUE_APP_GOOGLE_PLACES_API_KEY =
|
||||
"AIzaSyDptGCkOPgN2uWJOy4ou4M33phRD4MAoJo"
|
||||
|
||||
module.exports = {
|
||||
outputDir: "dist/fmg",
|
||||
publicPath: "/fmg",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
process.env.VUE_APP_CONSUMER_API_GATEWAY = "__VUE_APP_CONSUMER_API_GATEWAY__";
|
||||
process.env.VUE_APP_GOOGLE_PLACES_API_KEY = "__VUE_APP_GOOGLE_PLACES_API_KEY__";
|
||||
process.env.VUE_APP_HERITAGE_FUNNEL = "__VUE_APP_HERITAGE_FUNNEL__";
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue