Initial commit
This commit is contained in:
parent
2815d88858
commit
2d97232d40
6 changed files with 1801 additions and 64 deletions
1036
package-lock.json
generated
1036
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -40,6 +40,7 @@
|
||||||
"eslint-plugin-vue": "9.8.0",
|
"eslint-plugin-vue": "9.8.0",
|
||||||
"jest": "^27.0.5",
|
"jest": "^27.0.5",
|
||||||
"sass": "^1.32.7",
|
"sass": "^1.32.7",
|
||||||
"sass-loader": "^12.0.0"
|
"sass-loader": "^12.0.0",
|
||||||
|
"vitest": "^0.32.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
188
src/digital-components/textarea-question/textarea-question.vue
Normal file
188
src/digital-components/textarea-question/textarea-question.vue
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="textarea-question"
|
||||||
|
:class="(errors && errors.length) || hasError ? 'has-error' : ''">
|
||||||
|
<label
|
||||||
|
:for="inputId"
|
||||||
|
class="form-label">
|
||||||
|
{{ questionText }}
|
||||||
|
<span
|
||||||
|
v-if="!isRequired"
|
||||||
|
class="optional">(Optional)</span>
|
||||||
|
</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<textarea
|
||||||
|
:id="inputId"
|
||||||
|
:ref="inputId"
|
||||||
|
v-model.trim="value"
|
||||||
|
class="form-control"
|
||||||
|
:name="inputId"
|
||||||
|
:rows="[inputRows ?? 3]"
|
||||||
|
:placeholder="placeholderText"
|
||||||
|
:disabled="isDisabled"
|
||||||
|
:required="isRequired"
|
||||||
|
:maxlength="maxLength"
|
||||||
|
:validationRules="validationRules"
|
||||||
|
@paste="trimOnPaste"
|
||||||
|
@drop="trimOnPaste">
|
||||||
|
</textarea>
|
||||||
|
</div>
|
||||||
|
<p class="character-count margin-top-8">
|
||||||
|
{{ characterCount }}/{{ maxLength }} characters remaining
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
v-show="errorMessage"
|
||||||
|
class="row my-1 form-test-error">
|
||||||
|
<span
|
||||||
|
class="d-inline-flex mt-0"
|
||||||
|
role="alert">{{ errorMessage }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { useField } from 'vee-validate';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'textarea-question',
|
||||||
|
props: {
|
||||||
|
placeholderText: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
inputId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
modelValue: String,
|
||||||
|
isDisabled: Boolean,
|
||||||
|
isRequired: Boolean,
|
||||||
|
hasError: Boolean,
|
||||||
|
validationRules: String,
|
||||||
|
cmsWidgetName: String,
|
||||||
|
maxLength: String,
|
||||||
|
inputRows: Number
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue', 'textareaQuestionEvent.inputIdAssigned'],
|
||||||
|
setup(props) {
|
||||||
|
const propsClone = { ...props };
|
||||||
|
const { modelValue } = propsClone;
|
||||||
|
const initialValue = modelValue ?? '';
|
||||||
|
|
||||||
|
const fieldOptions = {
|
||||||
|
type: 'text',
|
||||||
|
value: modelValue,
|
||||||
|
initialValue
|
||||||
|
};
|
||||||
|
|
||||||
|
const { errorMessage,
|
||||||
|
handleChange,
|
||||||
|
validate,
|
||||||
|
errors }
|
||||||
|
= useField(props.inputId,
|
||||||
|
props.validationRules,
|
||||||
|
fieldOptions);
|
||||||
|
|
||||||
|
return {
|
||||||
|
errorMessage,
|
||||||
|
handleChange,
|
||||||
|
validate,
|
||||||
|
errors
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
characterCount() {
|
||||||
|
return this?.modelValue?.length ?? 0;
|
||||||
|
},
|
||||||
|
questionText() {
|
||||||
|
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
get() {
|
||||||
|
return this.modelValue;
|
||||||
|
},
|
||||||
|
set(newValue) {
|
||||||
|
this.$emit('update:modelValue', newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
async value(newValue) {
|
||||||
|
const result = await this.validate(newValue, this.validationRules);
|
||||||
|
if (result.valid) {
|
||||||
|
this.handleChange(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// TODO do we need to do anything else for this event?
|
||||||
|
this.$emit('textareaQuestionEvent.inputIdAssigned', this.inputId);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
trimOnPaste(evt) {
|
||||||
|
evt.stopPropagation();
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
const data = evt.type === 'paste' ? (evt.clipboardData || window.clipboardData) : evt.dataTransfer;
|
||||||
|
const value = data.getData('Text')?.trim();
|
||||||
|
|
||||||
|
this.$emit('update:modelValue', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.textarea-question {
|
||||||
|
label {
|
||||||
|
color: $black;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.form-test-error span{
|
||||||
|
color: $red;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
max-height: 5rem;
|
||||||
|
border: 1px solid $gray-500;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 12px 16px;
|
||||||
|
&::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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.optional {
|
||||||
|
color: $gray-550;
|
||||||
|
}
|
||||||
|
|
||||||
|
.character-count {
|
||||||
|
color: $gray-600;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-top-8 {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
395
src/layouts/contact-details/contact-details.spec.js
Normal file
395
src/layouts/contact-details/contact-details.spec.js
Normal file
|
|
@ -0,0 +1,395 @@
|
||||||
|
// Components
|
||||||
|
import contactDetails from '@/layouts/contact-details/contact-details.vue';
|
||||||
|
|
||||||
|
// TODO finish this file
|
||||||
|
|
||||||
|
// Supporting Files
|
||||||
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import { getMountOptions } from '@/helpers/unit-test-helper';
|
||||||
|
import { useMainStore } from '@/store';
|
||||||
|
import vehicleQuestionsMixin from '@/mixins/vehicle-questions-mixin';
|
||||||
|
import { nextTick } from 'vue';
|
||||||
|
import { vi } from 'vitest';
|
||||||
|
import baseMixin from '../../mixins/base-mixin';
|
||||||
|
|
||||||
|
// Mock our module for promises.
|
||||||
|
vi.mock('@/helpers/layout-helper.js', () =>
|
||||||
|
({
|
||||||
|
settleAllPromises: vi.fn()
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock fetchCmsContentForPage
|
||||||
|
vi.mock('@/helpers/cms-content-helper', () =>
|
||||||
|
({
|
||||||
|
fetchCmsContentForPage: vi.fn()
|
||||||
|
}));
|
||||||
|
|
||||||
|
const baseStoreGettersPageData = () =>
|
||||||
|
({
|
||||||
|
partsOrQuestions: [
|
||||||
|
{
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
childPartQuestions: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
contactDetails: [
|
||||||
|
{
|
||||||
|
questionSequence: 1,
|
||||||
|
questionText:
|
||||||
|
'Is your vehicle equipped with the optional Lane-Keeping System which tugs on the steering wheel and/or beeps to alert you if you drift too close to the edge of the lane?',
|
||||||
|
answers: [
|
||||||
|
{
|
||||||
|
answerResult1: 'DYNAMIC',
|
||||||
|
answerResult2: '1',
|
||||||
|
answerText: 'Yes',
|
||||||
|
nextQuestionSequence: null,
|
||||||
|
answerResult: 'DYNAMIC'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
answerResult1: 'Unknown',
|
||||||
|
answerResult2: '0',
|
||||||
|
answerText: 'No',
|
||||||
|
nextQuestionSequence: null,
|
||||||
|
answerResult: 'Unknown'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
partQuestions: [],
|
||||||
|
questions: [],
|
||||||
|
glassLocation: 'Windshield',
|
||||||
|
glassName: 'Single',
|
||||||
|
answerKey: 'Windshield-Single',
|
||||||
|
answerData: null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
const baseStoreGettersDamage = () =>
|
||||||
|
({
|
||||||
|
partsQuestionAnswers: [
|
||||||
|
{
|
||||||
|
glassLocation: 'Windshield',
|
||||||
|
glassName: 'Single',
|
||||||
|
result: 'FW04848',
|
||||||
|
answeredQuestions: [
|
||||||
|
{
|
||||||
|
questionText:
|
||||||
|
'Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?',
|
||||||
|
selectedAnswer: '1|nextQuestion|3|Yes',
|
||||||
|
selectedAnswerText: 'Yes',
|
||||||
|
questionNum: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionText:
|
||||||
|
'Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?',
|
||||||
|
selectedAnswer: '2|nextQuestion|3|Yes',
|
||||||
|
selectedAnswerText: 'Yes',
|
||||||
|
questionNum: 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
useMainStore().pageData = baseStoreGettersPageData;
|
||||||
|
useMainStore().damage = baseStoreGettersDamage;
|
||||||
|
|
||||||
|
describe('contactDetails.vue', () => {
|
||||||
|
describe('method arePagePrerequisitesValid...', () => {
|
||||||
|
test('Should return true for valid page requisites if pageData exists', () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = wrapper.vm.arePagePrerequisitesValid();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe(true);
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should return false for valid page requisites if partsOrQuestions in pageData is missing', () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
useMainStore().pageData = vi.fn(() =>
|
||||||
|
undefined);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = wrapper.vm.arePagePrerequisitesValid();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBeFalsy();
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should be at least one item in partsOrQuestions', () => {
|
||||||
|
// Arrange
|
||||||
|
useMainStore().pageData = vi.fn(() =>
|
||||||
|
({
|
||||||
|
partsOrQuestions: []
|
||||||
|
}));
|
||||||
|
useMainStore().damage = baseStoreGettersDamage;
|
||||||
|
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = wrapper.vm.arePagePrerequisitesValid();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBeFalsy();
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('watch on selectedAnswers should be set up...', () => {
|
||||||
|
test('Should trigger handleAnswerUpdates if watched data changes', async () => {
|
||||||
|
// Arrange
|
||||||
|
useMainStore().pageData = baseStoreGettersPageData;
|
||||||
|
useMainStore().damage = baseStoreGettersDamage;
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
const spy = vi.spyOn(wrapper.vm, 'handleAnswerUpdates');
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.setData({
|
||||||
|
selectedAnswers: {
|
||||||
|
'Windshield-Single': {
|
||||||
|
answerResult: 'FW04848',
|
||||||
|
answeredQuestions: [
|
||||||
|
{
|
||||||
|
questionText:
|
||||||
|
'Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?',
|
||||||
|
selectedAnswer: '1|nextQuestion|3|Yes',
|
||||||
|
selectedAnswerText: 'Yes',
|
||||||
|
questionNum: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionText:
|
||||||
|
'Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?',
|
||||||
|
selectedAnswer: '2|nextQuestion|3|Yes',
|
||||||
|
selectedAnswerText: 'Yes',
|
||||||
|
questionNum: 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
index: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('forwardButtonAction', () => {
|
||||||
|
test('Should clear out answerData', () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.questionsData = [
|
||||||
|
{
|
||||||
|
glassLocation: 'Windshield',
|
||||||
|
glassName: 'Single',
|
||||||
|
answerData: {
|
||||||
|
answerResult: 'FW04848',
|
||||||
|
answeredQuestions: []
|
||||||
|
},
|
||||||
|
questions: [],
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
childPartQuestions: [{}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
wrapper.vm.dispatchStoreAction = vi.fn(() =>
|
||||||
|
({
|
||||||
|
data: []
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.forwardButtonAction();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.questionsData[0].answerData).toEqual({});
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should save to pinia store', async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.questionsData = [
|
||||||
|
{
|
||||||
|
glassLocation: 'Windshield',
|
||||||
|
glassName: 'Single',
|
||||||
|
answerData: {
|
||||||
|
answerResult: 'FW04848',
|
||||||
|
answeredQuestions: []
|
||||||
|
},
|
||||||
|
questions: [],
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
childPartQuestions: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
useMainStore().getPartsOrQuestions = vi.fn(() =>
|
||||||
|
({
|
||||||
|
data: {
|
||||||
|
partsOrQuestions: []
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.forwardButtonAction();
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.saveCapabilityQuestionAnswers).toHaveBeenCalled;
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
test('Should call GET_PART_FROM_CAPABILITY_QUESTION_ANSWER API', async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.questionsData = [
|
||||||
|
{
|
||||||
|
glassLocation: 'Windshield',
|
||||||
|
glassName: 'Single',
|
||||||
|
answerData: {
|
||||||
|
answerResult: 'FW04848',
|
||||||
|
answeredQuestions: []
|
||||||
|
},
|
||||||
|
questions: [],
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
childPartQuestions: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
wrapper.vm.dispatchStoreAction = vi.fn(() =>
|
||||||
|
({
|
||||||
|
data: []
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.forwardButtonAction();
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.getPartFromCapabilityQuestionAnswer).toHaveBeenCalled;
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should trigger navigateForward', async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.questionsData = [
|
||||||
|
{
|
||||||
|
glassLocation: 'Windshield',
|
||||||
|
glassName: 'Single',
|
||||||
|
answerData: {
|
||||||
|
answerResult: 'FW04848',
|
||||||
|
answeredQuestions: []
|
||||||
|
},
|
||||||
|
questions: [],
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
childPartQuestions: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
useMainStore().getPartsOrQuestions = vi.fn(() =>
|
||||||
|
({
|
||||||
|
data: {
|
||||||
|
partsOrQuestions: []
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
wrapper.vm.navigateForward = vi.fn();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await wrapper.vm.forwardButtonAction();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.navigateForward).toHaveBeenCalled();
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupMocks({
|
||||||
|
mountOptionsMockData = {
|
||||||
|
router: {
|
||||||
|
navigate: vi.fn()
|
||||||
|
},
|
||||||
|
actionList: [
|
||||||
|
{
|
||||||
|
actionName: 'saveCapabilityQuestionAnswers',
|
||||||
|
data: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
actionName: 'getPartsOrQuestions',
|
||||||
|
data: {}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
route: {
|
||||||
|
query: {
|
||||||
|
issPage: 'capability-questions'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
computedSwitcher: [
|
||||||
|
{
|
||||||
|
glassLocation: 'Windshield',
|
||||||
|
glassName: 'Single',
|
||||||
|
answerData: {
|
||||||
|
answerResult: 'FW04848',
|
||||||
|
answeredQuestions: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
questionsData: {
|
||||||
|
get() {
|
||||||
|
return this.computedSwitcher;
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.computedSwitcher = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
useMainStore().getPartsOrQuestions = vi.fn(() =>
|
||||||
|
({
|
||||||
|
data: {
|
||||||
|
partsOrQuestions: []
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
const mountOptions = getMountOptions({
|
||||||
|
...mountOptionsMockData,
|
||||||
|
mixins: [baseMixin, vehicleQuestionsMixin]
|
||||||
|
});
|
||||||
|
mountOptions.attachTo = document.body;
|
||||||
|
|
||||||
|
const wrapper = shallowMount(contactDetails, mountOptions);
|
||||||
|
|
||||||
|
return { wrapper };
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,91 @@
|
||||||
<template>
|
<template>
|
||||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }" >
|
<Form
|
||||||
|
ref="contact-details-form"
|
||||||
|
v-slot="{ meta }"
|
||||||
|
@submit="onSubmit"
|
||||||
|
@invalidSubmit="onInvalidSubmit">
|
||||||
<div class="page-container-grouped-styles">
|
<div class="page-container-grouped-styles">
|
||||||
<div class="fade-on-route-transition position-relative">
|
<div class="fade-on-route-transition position-relative">
|
||||||
<siteHeader cmsWidgetName="SiteHeaderWidget"/>
|
<siteHeader cmsWidgetName="SiteHeaderWidget" />
|
||||||
<div class="container-fluid pb-2">
|
<div class="container-fluid pb-2">
|
||||||
<p>Placeholder for contact details page</p>
|
<siteSubHeader
|
||||||
|
id="sub-header"
|
||||||
|
class="margin-top-16"
|
||||||
|
cmsWidgetName="SiteSubHeaderWidget" />
|
||||||
|
<textboxQuestion
|
||||||
|
ref="firstName"
|
||||||
|
v-model="firstName"
|
||||||
|
class="margin-top-24"
|
||||||
|
inputId="firstName"
|
||||||
|
cmsWidgetName="FirstNameQuestionWidget"
|
||||||
|
isRequired
|
||||||
|
validationRules="first-name-required" />
|
||||||
|
<textboxQuestion
|
||||||
|
ref="lastName"
|
||||||
|
v-model="lastName"
|
||||||
|
class="margin-top-16"
|
||||||
|
inputId="lastName"
|
||||||
|
cmsWidgetName="LastNameQuestionWidget"
|
||||||
|
isRequired
|
||||||
|
validationRules="last-name-required" />
|
||||||
|
<textboxQuestion
|
||||||
|
ref="email"
|
||||||
|
v-model="email"
|
||||||
|
class="margin-top-16"
|
||||||
|
inputId="email"
|
||||||
|
cmsWidgetName="EmailQuestionWidget"
|
||||||
|
isRequired
|
||||||
|
validationRules="email-address-required|email-address-format" />
|
||||||
|
<textboxQuestion
|
||||||
|
ref="firstName"
|
||||||
|
v-model="phoneNumber"
|
||||||
|
class="margin-top-16"
|
||||||
|
inputId="phoneNumber"
|
||||||
|
cmsWidgetName="PhoneNumberQuestionWidget"
|
||||||
|
isRequired
|
||||||
|
validationRules="phone-number-required|phone-number-format" />
|
||||||
|
<checkbox
|
||||||
|
ref="requestTextUpdates"
|
||||||
|
v-model="requestTextUpdates"
|
||||||
|
class="margin-top-8"
|
||||||
|
checkboxName="requestTextUpdates"
|
||||||
|
buttonID="requestTextUpdates"
|
||||||
|
:checkboxLabel="textContentText" />
|
||||||
|
|
||||||
|
<textareaQuestion
|
||||||
|
v-model="notesForTechnician"
|
||||||
|
class="margin-top-16"
|
||||||
|
inputId="technicianNotes"
|
||||||
|
:isDisabled="false"
|
||||||
|
:isRequired="false"
|
||||||
|
hasError="hasError"
|
||||||
|
cmsWidgetName="NotesQuestionWidget"
|
||||||
|
maxLength="500"
|
||||||
|
inputRows="30" />
|
||||||
|
|
||||||
|
<p class="disclaimer margin-top-24">
|
||||||
|
{{ textUpdateDisclaimerText }} I also agree to Safelite's
|
||||||
|
<textLink
|
||||||
|
class="disclaimer-link"
|
||||||
|
linkType="text"
|
||||||
|
text="Privacy Policy"
|
||||||
|
href="//www.safelite.com/privacy-center"
|
||||||
|
target="_blank" />
|
||||||
|
and
|
||||||
|
<textLink
|
||||||
|
class="disclaimer-link"
|
||||||
|
linkType="text"
|
||||||
|
text="Terms of Use"
|
||||||
|
href="//www.safelite.com/terms-of-use"
|
||||||
|
target="_blank" />.
|
||||||
|
</p>
|
||||||
|
|
||||||
<siteFooter
|
<siteFooter
|
||||||
cmsWidgetName="SiteFooterWidget"
|
ref="siteFooter"
|
||||||
ref="siteFooter"
|
cmsWidgetName="SiteFooterWidget"
|
||||||
:isForwardActionDisabled="!meta.valid"
|
:isForwardActionDisabled="!meta.valid"
|
||||||
@ForwardClicked="navigateForward"
|
@forwardClicked="navigateForward"
|
||||||
@back-clicked="backButtonAction"
|
@backClicked="backButtonAction" />
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -19,67 +93,132 @@
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
// Components
|
// Components
|
||||||
import siteHeader from '@/iss-components/site-header/site-header';
|
import siteHeader from '@/iss-components/site-header/site-header.vue';
|
||||||
import siteFooter from "@/iss-components/site-footer/site-footer";
|
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
||||||
|
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
|
||||||
|
import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue';
|
||||||
|
import checkbox from '@/ux-components/checkbox/checkbox.vue';
|
||||||
|
import textareaQuestion from '@/digital-components/textarea-question/textarea-question.vue';
|
||||||
|
import textLink from '@/ux-components/text-link/text-link.vue';
|
||||||
|
|
||||||
// Supporting files
|
// Supporting files
|
||||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
|
||||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
import { settleAllPromises } from '@/helpers/layout-helper';
|
||||||
import { Form } from "vee-validate";
|
import { Form, defineRule } from 'vee-validate';
|
||||||
import BaseFormMixin from '@/mixins/base-form-mixin.js';
|
import { errorMessages } from '@/constants/error-messages';
|
||||||
|
import BaseFormMixin from '@/mixins/base-form-mixin';
|
||||||
import { useMainStore } from '@/store';
|
import { useMainStore } from '@/store';
|
||||||
|
import { required, regex } from '@/helpers/validation-rules';
|
||||||
|
|
||||||
|
// TODO define these globally and reuse
|
||||||
|
defineRule('first-name-required', required(errorMessages.FIRST_NAME_REQUIRED));
|
||||||
|
defineRule('last-name-required', required(errorMessages.LAST_NAME_REQUIRED));
|
||||||
|
defineRule('phone-number-required', required(errorMessages.PHONE_NUMBER_REQUIRED));
|
||||||
|
defineRule('phone-number-format',
|
||||||
|
regex(/^(\([0-9]{3}\)|[0-9]{3}) *[-.]? *[0-9]{3} *[-.]? *[0-9]{4}$/,
|
||||||
|
errorMessages.PHONE_NUMBER_FORMAT));
|
||||||
|
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));
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "contact-details",
|
name: 'contact-details',
|
||||||
|
components: {
|
||||||
|
siteHeader,
|
||||||
|
siteSubHeader,
|
||||||
|
textboxQuestion,
|
||||||
|
checkbox,
|
||||||
|
textareaQuestion,
|
||||||
|
siteFooter,
|
||||||
|
// eslint-disable-next-line vue/no-reserved-component-names
|
||||||
|
Form,
|
||||||
|
textLink
|
||||||
|
},
|
||||||
mixins: [BaseFormMixin],
|
mixins: [BaseFormMixin],
|
||||||
data() {
|
async beforeRouteEnter(to, from, next) {
|
||||||
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const mainStore = useMainStore();
|
|
||||||
return { mainStore };
|
|
||||||
},
|
|
||||||
async beforeRouteEnter(to, from, next)
|
|
||||||
{
|
|
||||||
// Call APIs
|
// Call APIs
|
||||||
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
|
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
|
||||||
|
|
||||||
// Settle promises and get results
|
// Settle promises and get results
|
||||||
const promiseResultMap = [
|
const promiseResultMap = [
|
||||||
{
|
{
|
||||||
resultKey: "cmsContent",
|
resultKey: 'cmsContent',
|
||||||
promise: cmsContentPromise,
|
promise: cmsContentPromise
|
||||||
},];
|
}
|
||||||
|
];
|
||||||
|
|
||||||
//use resultMap to populate layout content.
|
// use resultMap to populate layout content.
|
||||||
let resultMap = await settleAllPromises(promiseResultMap);
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
|
|
||||||
next((vm) => {
|
next((vm) => {
|
||||||
vm.setCmsContent(resultMap.cmsContent);
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
firstName: useMainStore().order.customer.firstName,
|
||||||
|
lastName: useMainStore().order.customer.lastName,
|
||||||
|
email: useMainStore().order.customer.emailAddress,
|
||||||
|
phoneNumber: useMainStore().order.customer.phoneNumber,
|
||||||
|
requestTextUpdates: false,
|
||||||
|
notesForTechnician: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
textContentText() {
|
||||||
|
return `${this.getCmsContent('TextContentWidget', 'Text')}*`;
|
||||||
},
|
},
|
||||||
methods:
|
textUpdateDisclaimerText() {
|
||||||
{
|
return `*${this.getCmsContent('TextUpdateDisclaimerWidget', 'Text')}`;
|
||||||
backButtonAction() {
|
}
|
||||||
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
|
},
|
||||||
},
|
methods:
|
||||||
|
{
|
||||||
forwardButtonAction() {
|
backButtonAction() {
|
||||||
},
|
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||||
|
|
||||||
navigateForward() {
|
|
||||||
this.$router.navigate(
|
|
||||||
this.navigationScenarios.CLICKED_FORWARD,
|
|
||||||
this.$route
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
components: {
|
|
||||||
siteHeader,
|
forwardButtonAction() {
|
||||||
siteFooter,
|
this.navigateForward();
|
||||||
Form,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
navigateForward() {
|
||||||
|
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD,
|
||||||
|
this.$route);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
#sub-header {
|
||||||
|
span {
|
||||||
|
color: $black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-top-24 {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-top-16 {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-top-8 {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disclaimer {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $darker-gray;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disclaimer-link {
|
||||||
|
text-decoration: none;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue