Merge branch 'develop' into CSR-1382-order-quote-initial-setup

This commit is contained in:
Bryan Mauger 2023-09-11 10:45:09 -04:00
commit 6085cd1b13
26 changed files with 1123 additions and 680 deletions

View file

@ -4,7 +4,7 @@ const AppointmentTypeStrings = {
DROP_OFF: "Dropoff", DROP_OFF: "Dropoff",
}; };
const PREMIUM_TIME_SLOT_ID_FLAG = "-premium"; const PREMIUM_TIME_SLOT_ID_FLAG = "-PREMIUM";
const PREMIUM_FEE_PART_TYPE = "EARLY BIRD"; const PREMIUM_FEE_PART_TYPE = "EARLY BIRD";

View file

@ -26,7 +26,7 @@
{{ value }} {{ value }}
</option> </option>
</select> </select>
<div v-show="errorMessage" class="row mt-2 form-test-error"> <div v-show="errorMessage && this.disableValidation" class="row mt-2 form-test-error">
<span aria-atomic="true" aria-live="polite">{{ errorMessage }}</span> <span aria-atomic="true" aria-live="polite">{{ errorMessage }}</span>
</div> </div>
</div> </div>
@ -56,6 +56,11 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
disableValidationIfElementDisabled: {
type: Boolean,
required: false,
default: false,
},
}, },
setup(props) { setup(props) {
const uuid = uuidv4(); const uuid = uuidv4();
@ -110,6 +115,9 @@ export default {
this.$emit("update:modelValue", newValue); this.$emit("update:modelValue", newValue);
}, },
}, },
disableValidation() {
return this.disableValidationIfElementDisabled ? !this.isDisabled : true;
},
}, },
watch: { watch: {
selectedOption(newValue) { selectedOption(newValue) {

View file

@ -2,13 +2,12 @@
<div class="text-block w-100" :class="[justifyText, typeStyle, fontWeight, marginTopClass]"> <div class="text-block w-100" :class="[justifyText, typeStyle, fontWeight, marginTopClass]">
<span v-for="copy in splitCopyOnCMSPlaceHolder(this.textBlockCopy)" :key="copy"> <span v-for="copy in splitCopyOnCMSPlaceHolder(this.textBlockCopy)" :key="copy">
<span v-if="doesCopyContainRouterLink(copy)"> <span v-if="doesCopyContainRouterLink(copy)">
<router-link <textLink
:to="{ linkType="text"
query: { [pageQueryString]: `${getRouterLinkRouteFromCopy(copy)}` }, :text="getRouterLinkDisplayTextFromCopy(copy)"
name: 'root', href="javascript:void(0)"
}" useLoadingModal
>{{ getRouterLinkDisplayTextFromCopy(copy) }}</router-link @click-event="navigateWithScenario(getRouterLinkRouteFromCopy(copy))" />
>
</span> </span>
<span v-else-if="doesCopyContainTextLink(copy)"> <span v-else-if="doesCopyContainTextLink(copy)">
<textLink <textLink
@ -55,6 +54,9 @@ export default {
getRouterLinkRouteFromCopy, getRouterLinkRouteFromCopy,
getRouterLinkDisplayTextFromCopy, getRouterLinkDisplayTextFromCopy,
getExternalLink, getExternalLink,
navigateWithScenario(scenarioName) {
this.$router.navigateWithoutSaving(scenarioName, this.$route);
},
}, },
computed: { computed: {
pageQueryString() { pageQueryString() {

View file

@ -20,6 +20,7 @@
<textLink <textLink
linkType="navigation" linkType="navigation"
:text="backLink" :text="backLink"
useLoadingModal
@click-event="linkClick" @click-event="linkClick"
href="javascript:void(0)" href="javascript:void(0)"
data-bs-target="#footerModal" data-bs-target="#footerModal"

View file

@ -1,5 +1,7 @@
<template> <template>
<div v-if="isModalVisible" class="container-fluid modal-loader"> <div v-if="isModalVisible">
<div class="dimmer-overlay"></div>
<div class="container-fluid modal-loader" :class="layoutClass">
<div class="row g-2 h-100 d-flex align-items-center"> <div class="row g-2 h-100 d-flex align-items-center">
<div class="container-fluid overflow-hidden"> <div class="container-fluid overflow-hidden">
<div class="row h-100"> <div class="row h-100">
@ -48,6 +50,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
@ -58,6 +61,17 @@ export default {
isModalVisible: false, isModalVisible: false,
}; };
}, },
props: {
notFullScreen: {
type: Boolean,
default: false,
},
},
computed: {
layoutClass() {
return this.notFullScreen ? "card-layout" : "full-screen";
},
},
methods: { methods: {
showModal() { showModal() {
//Display modal //Display modal
@ -80,20 +94,43 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
.modal-loader { .dimmer-overlay {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
right: 0;
bottom: 0;
z-index: 1057;
width: 100%;
height: 100%; height: 100%;
width: 100%;
background-color: $black;
opacity: 0.4;
z-index: 1056;
}
.modal-loader {
position: fixed;
z-index: 1057;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
outline: 0; outline: 0;
background: $gray-100; background: $gray-100;
&.full-screen {
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
}
&.card-layout {
height: 186px;
width: calc(100% - 3rem);
border-radius: 0.5rem;
position: absolute;
top: calc(50% - 93px);
left: 1.5rem;
}
.tagbg { .tagbg {
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='none' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 84 32'%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' fill='%23fff'/%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' fill='%23fff'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06.8c8.08 0 11.89.935 11.89.935 3.58.576 5.696 7.207 5.696 7.207h.727c0-2.427 1.302-2.397 2.341-2 .723.292 1.363.76 1.86 1.361 1.319 1.547.465 1.674.465 1.674h-5.067l3.846 3.01v12.016c0 2.41-2.029 2.31-2.029 2.31H22.338s-2.029.1-2.029-2.31V12.996l3.84-3.009H19.07s-.853-.127.466-1.674a4.693 4.693 0 0 1 1.86-1.361c1.032-.397 2.341-.427 2.341 2h.736s2.117-6.64 5.696-7.21c0 0 3.81-.942 11.89-.942Z' fill='%23fff' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06 8.847c7.924 0 14.685.511 14.685.511 0-2.585-2.38-6.011-2.38-6.011S50.4 2.519 42.06 2.519s-12.303.828-12.303.828-2.38 3.426-2.38 6.011c0 0 6.76-.51 14.683-.51Z' fill='%23DA291C' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M35.277 16.523a62.558 62.558 0 0 1 13.277 0m3.276-.439s2.384-2.257 8.608-2.257c0 0 1.179 2.657-2.54 3.37m-25.894-1.113s-2.387-2.257-8.611-2.257c0 0-1.16 2.579 2.543 3.37m-3.546 5.856s21.52 3.647 39.123 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e"); background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='none' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 84 32'%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' fill='%23fff'/%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' fill='%23fff'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06.8c8.08 0 11.89.935 11.89.935 3.58.576 5.696 7.207 5.696 7.207h.727c0-2.427 1.302-2.397 2.341-2 .723.292 1.363.76 1.86 1.361 1.319 1.547.465 1.674.465 1.674h-5.067l3.846 3.01v12.016c0 2.41-2.029 2.31-2.029 2.31H22.338s-2.029.1-2.029-2.31V12.996l3.84-3.009H19.07s-.853-.127.466-1.674a4.693 4.693 0 0 1 1.86-1.361c1.032-.397 2.341-.427 2.341 2h.736s2.117-6.64 5.696-7.21c0 0 3.81-.942 11.89-.942Z' fill='%23fff' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06 8.847c7.924 0 14.685.511 14.685.511 0-2.585-2.38-6.011-2.38-6.011S50.4 2.519 42.06 2.519s-12.303.828-12.303.828-2.38 3.426-2.38 6.011c0 0 6.76-.51 14.683-.51Z' fill='%23DA291C' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M35.277 16.523a62.558 62.558 0 0 1 13.277 0m3.276-.439s2.384-2.257 8.608-2.257c0 0 1.179 2.657-2.54 3.37m-25.894-1.113s-2.387-2.257-8.611-2.257c0 0-1.16 2.579 2.543 3.37m-3.546 5.856s21.52 3.647 39.123 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
background-repeat: no-repeat; background-repeat: no-repeat;

View file

@ -10,6 +10,7 @@
linkType="textSmall" linkType="textSmall"
text="Edit" text="Edit"
class="ml-auto" class="ml-auto"
useLoadingModal
@click-event="linkClicked" @click-event="linkClicked"
href="javascript:void(0)" /> href="javascript:void(0)" />
</div> </div>

View file

@ -1,150 +0,0 @@
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import customerDetailsModalQuestion from "@/layouts/review/review-sections/customer-review/customer-details-modal-question/customer-details-modal-question";
const testConstants = {
previousCustomerValues: {
firstName: "First",
lastName: "Last",
emailAddress: "builddigitaltest@safelite.com",
phoneNumber: "111-111-1111",
isSmsOptIn: false,
},
newValues: {
firstName: "New First",
lastName: "New Last",
emailAddress: "builddigitaltest2@safelite.com",
phoneNumber: "222-222-2222",
isSmsOptIn: true,
},
};
let cmsContent;
describe("Customer Details Modal", () => {
beforeEach(() => {
cmsContent = {};
});
describe("Submit", () => {
test("Should push to store if a field has changed", async () => {
// Arrange
let props = generateDefaultProps();
const { wrapper } = setupMocks({
propsData: props,
});
// Act
wrapper.vm.onModalOpened();
wrapper.vm.firstName = testConstants.newValues.firstName;
await wrapper.vm.setContactDetails();
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalled();
expect(wrapper.vm.closeModal).toHaveBeenCalled();
});
test("Should not push to store if no fields have changed", async () => {
// Arrange
let props = generateDefaultProps();
const { wrapper } = setupMocks({
propsData: props,
});
// Act
wrapper.vm.onModalOpened();
await wrapper.vm.setContactDetails();
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.dispatchStoreAction).not.toHaveBeenCalled();
expect(wrapper.vm.closeModal).toHaveBeenCalled();
});
});
describe("Default values", () => {
test("Should populate on open", () => {
// Arrange
let props = generateDefaultProps();
const { wrapper } = setupMocks({
propsData: props,
});
// Act
wrapper.vm.onModalOpened();
// Assert
expect(wrapper.vm.firstName).toBe(testConstants.previousCustomerValues.firstName);
expect(wrapper.vm.lastName).toBe(testConstants.previousCustomerValues.lastName);
expect(wrapper.vm.emailAddress).toBe(testConstants.previousCustomerValues.emailAddress);
expect(wrapper.vm.phoneNumber).toBe(testConstants.previousCustomerValues.phoneNumber);
expect(wrapper.vm.isSmsOptIn).toBe(testConstants.previousCustomerValues.isSmsOptIn);
});
test("Should replace old values on open", async () => {
// Arrange
let props = generateDefaultProps();
const { wrapper } = setupMocks({
propsData: props,
});
// Act
wrapper.vm.firstName = testConstants.newValues.firstName;
wrapper.vm.lastName = testConstants.newValues.lastName;
wrapper.vm.emailAddress = testConstants.newValues.emailAddress;
wrapper.vm.phoneNumber = testConstants.newValues.phoneNumber;
wrapper.vm.isSmsOptIn = testConstants.newValues.isSmsOptIn;
wrapper.vm.onModalOpened();
// Assert
expect(wrapper.vm.firstName).toBe(testConstants.previousCustomerValues.firstName);
expect(wrapper.vm.lastName).toBe(testConstants.previousCustomerValues.lastName);
expect(wrapper.vm.emailAddress).toBe(testConstants.previousCustomerValues.emailAddress);
expect(wrapper.vm.phoneNumber).toBe(testConstants.previousCustomerValues.phoneNumber);
expect(wrapper.vm.isSmsOptIn).toBe(testConstants.previousCustomerValues.isSmsOptIn);
});
});
});
function generateDefaultProps() {
return {
previousCustomerValues: {
firstName: testConstants.previousCustomerValues.firstName,
lastName: testConstants.previousCustomerValues.lastName,
emailAddress: testConstants.previousCustomerValues.emailAddress,
phoneNumber: testConstants.previousCustomerValues.phoneNumber,
isSmsOptIn: testConstants.previousCustomerValues.isSmsOptIn,
},
};
}
function setupMocks(customMountOptions) {
const mountOptions = getMountOptions(customMountOptions);
const mockMixin = {
methods: {
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
return cmsContent?.[widgetName]?.[cmsFieldName] ?? "";
}),
},
};
mountOptions.global.mixins = [mockMixin];
const wrapper = shallowMount(customerDetailsModalQuestion, mountOptions);
wrapper.vm.setCmsContent = jest.fn();
wrapper.vm.openModal = jest.fn();
wrapper.vm.closeModal = jest.fn();
wrapper.vm.dispatchStoreAction = jest.fn();
return { wrapper };
}

View file

@ -1,148 +0,0 @@
<template>
<transition name="fade" mode="out-in">
<div class="customer-details-question">
<modal
ref="CustomerDetailsModal"
headerText="Contact details"
footerButtonText="Save contact details"
:onModalOpenedCallback="onModalOpened"
:onModalClosedCallback="onModalClosed"
@isModalOpened="setModalStatus"
@footer-button-event="setContactDetails">
<template v-if="isModalOpened">
<textboxQuestion
class="mb-4"
cmsWidgetName="CustomerFirstNameWidget"
v-model="firstName"
ref="firstName"
customInputId="firstName"
validation-rules="first-name-required" />
<textboxQuestion
class="mb-4"
cmsWidgetName="CustomerLastNameWidget"
v-model="lastName"
ref="lastName"
customInputId="lastName"
validation-rules="last-name-required" />
<textboxQuestion
class="mb-4"
cmsWidgetName="CustomerEmailWidget"
v-model="emailAddress"
ref="emailAddress"
customInputId="emailAddress"
validation-rules="email-address-required|email-address-format" />
<phoneNumberQuestion
class="mb-4"
cmsWidgetName="CustomerPhoneWidget"
v-model="phoneNumber"
ref="phoneNumber"
isRequired
validation-rules="phone-number-required" />
<checkboxQuestion
class="mb-5"
cmsWidgetName="CustomerTextMeWidget"
v-model="isSmsOptIn" />
<textBlock cmsWidgetName="CustomerTextDisclaimerWidget" typeStyle="caption" />
</template>
</modal>
</div>
</transition>
</template>
<script>
import modal from "@/digital-components/modal/modal";
import textboxQuestion from "@/digital-components/textbox-question/textbox-question";
import phoneNumberQuestion from "@/digital-components/phone-number-question/phone-number-question";
import checkboxQuestion from "@/digital-components/checkbox-question/checkbox-question";
import textBlock from "@/digital-components/text-block/text-block";
import { errorMessages } from "@/constants/error-messages";
import { required, regex } from "@/helpers/validation-rules";
import { defineRule } from "vee-validate";
// DEFINE VALIDATION RULES
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_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
)
);
export default {
name: "customer-details-modal-question",
data() {
return {
isModalOpened: false,
firstName: "",
lastName: "",
emailAddress: "",
phoneNumber: "",
isSmsOptIn: false,
};
},
props: {
previousCustomerValues: Object,
},
computed: {
modal() {
return this.$refs.CustomerDetailsModal;
},
haveCustomerDetailsChanged() {
return (
this.firstName !== this.previousCustomerValues?.firstName ||
this.lastName !== this.previousCustomerValues?.lastName ||
this.emailAddress !== this.previousCustomerValues?.emailAddress ||
this.phoneNumber !== this.previousCustomerValues?.phoneNumber ||
this.isSmsOptIn !== this.previousCustomerValues?.isSmsOptIn
);
},
},
methods: {
openModal() {
this.modal.openModal();
},
closeModal() {
this.modal.closeModal();
},
onModalOpened() {
this.firstName = this.previousCustomerValues?.firstName ?? "";
this.lastName = this.previousCustomerValues?.lastName ?? "";
this.emailAddress = this.previousCustomerValues?.emailAddress ?? "";
this.phoneNumber = this.previousCustomerValues?.phoneNumber ?? "";
this.isSmsOptIn = this.previousCustomerValues?.isSmsOptIn ?? false;
},
onModalClosed() {},
setModalStatus(isOpened) {
this.isModalOpened = isOpened;
},
async setContactDetails() {
if (this.haveCustomerDetailsChanged) {
await this.dispatchStoreAction(
this.storeActions.SAVE_CUSTOMER_DETAILS,
{
firstName: this.firstName,
lastName: this.lastName,
emailAddress: this.emailAddress,
phoneNumber: this.phoneNumber,
isSmsOptIn: this.isSmsOptIn,
},
false
);
}
this.closeModal();
},
},
components: {
modal,
textboxQuestion,
phoneNumberQuestion,
checkboxQuestion,
textBlock,
},
};
</script>

View file

@ -9,11 +9,7 @@ const testConstants = {
text: "Header", text: "Header",
}, },
sms: { sms: {
template: "Test {custom:smsOptInJoiner}", text: "Sms",
expected: {
ifTrue: "Test in to",
ifFalse: "Test out of",
},
}, },
}, },
customer: { customer: {
@ -27,7 +23,7 @@ const testConstants = {
fullName: "First Last", fullName: "First Last",
phoneNumber: "111-111-1111", phoneNumber: "111-111-1111",
emailAddress: "builddigitaltest@safelite.com", emailAddress: "builddigitaltest@safelite.com",
smsOptIn: "Test out of", smsOptIn: "Sms",
}, },
}; };
@ -38,7 +34,7 @@ describe("Customer Review Block", () => {
cmsContent = { cmsContent = {
CustomerWidget: { CustomerWidget: {
HeaderText: testConstants.cms.header.text, HeaderText: testConstants.cms.header.text,
SubheaderText: testConstants.cms.sms.template, SubheaderText: testConstants.cms.sms.text,
}, },
}; };
}); });
@ -58,11 +54,9 @@ describe("Customer Review Block", () => {
expect(wrapper.vm.header).toEqual(testConstants.cms.header.text); expect(wrapper.vm.header).toEqual(testConstants.cms.header.text);
}); });
describe("SMS Opt In Text", () => { test("Should display sms text from cms", async () => {
test("Should render correctly when opt in is true:", async () => {
// Arrange // Arrange
let props = generateDefaultProps(); let props = generateDefaultProps();
props.customer.isSmsOptIn = true;
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
propsData: props, propsData: props,
@ -72,38 +66,7 @@ describe("Customer Review Block", () => {
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
// Assert // Assert
expect(wrapper.vm.smsOptIn).toEqual(testConstants.cms.sms.expected.ifTrue); expect(wrapper.vm.smsOptIn).toEqual(testConstants.cms.sms.text);
});
test("Should render correctly when opt in is false:", async () => {
// Arrange
let props = generateDefaultProps();
props.customer.isSmsOptIn = false;
const { wrapper } = setupMocks({
propsData: props,
});
// Act
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.smsOptIn).toEqual(testConstants.cms.sms.expected.ifFalse);
});
test("Should render correctly when opt in is null:", async () => {
// Arrange
let props = generateDefaultProps();
props.customer.isSmsOptIn = null;
const { wrapper } = setupMocks({
propsData: props,
});
// Act
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.smsOptIn).toEqual(testConstants.cms.sms.expected.ifFalse);
});
}); });
test("Should render correct display content", async () => { test("Should render correct display content", async () => {

View file

@ -36,10 +36,7 @@ export default {
return this.customer?.phoneNumber; return this.customer?.phoneNumber;
}, },
smsOptIn() { smsOptIn() {
const rawCmsText = this.getCmsContent(this.cmsWidgetName, "SubheaderText"); return this.getCmsContent(this.cmsWidgetName, "SubheaderText");
const joinerText = this.customer?.isSmsOptIn ? "in to" : "out of";
return rawCmsText.replace("{custom:smsOptInJoiner}", joinerText);
}, },
}, },
components: { components: {

View file

@ -1,3 +1,362 @@
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import store from "@/store";
import review from "@/layouts/review/review";
const testConstants = {};
jest.mock("@/store", () => ({
commit: jest.fn(),
dispatch: jest.fn(),
}));
describe("Review Page", () => { describe("Review Page", () => {
test.todo("Add more tests as specific functionality is added."); beforeEach(() => {
store.getters = {
order: {
vehicle: {
year: "2020",
make: "Acura",
model: "MDX",
style: "4 door sedan",
},
damage: {
isRepair: false,
numberOfChips: 2,
glassToReplace: ["dummy location value"],
},
lineItems: {
glassParts: ["dummy part value"],
supportingItems: ["dummy supporting item"],
vaps: ["dummy vap"],
},
serviceLocation: {
address: "address 1",
address2: "address 2",
city: "city",
state: "state",
zipCode: "zip code",
appointmentType: "Mobile",
provider: {
providerNumber: 1,
address: {
streetAddress: "provider address 1",
city: "provider city",
state: "provider state",
zipCode: "provider zip code",
},
},
},
schedule: {
date: "date",
startTime: "start",
endTime: "end",
jobMinMinutes: "30",
jobMaxMinutes: "45",
},
customer: {
firstName: "first name",
lastName: "last name",
emailAddress: "builddigitaltest@safelite.com",
phoneNumber: "555-555-5555",
isSmsOptIn: true,
},
},
};
}); });
describe("arePagePrerequisitesValid", () => {
test("Returns true for baseline valid state", () => {
// Arrange
const { wrapper } = setupMocks({});
// Act
const isValid = wrapper.vm.arePagePrerequisitesValid();
// Assert
expect(isValid).toBe(true);
});
test("Returns false for empty state", () => {
// Arrange
store.getters.order = {
vehicle: {
year: null,
make: null,
model: null,
style: null,
carId: null,
category: null,
vin: null,
imageUrl: null,
imageVifNumber: null,
imageColor: null,
registration: {
licensePlate: null,
},
},
serviceLocation: {
address: null,
address2: null,
city: null,
state: null,
zipCode: null,
zipCodeCtu: null,
appointmentType: null,
isVehicleProtected: null,
provider: {
providerNumber: null,
address: {
streetAddress: null,
city: null,
state: null,
zipCode: null,
zipCodeCtu: null,
},
},
techNotes: null,
},
customer: {
firstName: null,
lastName: null,
emailAddress: null,
phoneNumber: null,
isSmsOptIn: null,
},
damage: {
isRepair: null,
numberOfChips: null,
glassToReplace: null,
partQuestionAnswers: null,
moldingQuestionAnswers: null,
capabilityQuestionAnswers: null,
},
lineItems: {
glassParts: null,
supportingItems: null,
vaps: null,
serverData: null,
},
payment: {
isInsurance: null,
insuranceCoverage: {
isVerified: null,
coverageStatus: null,
},
parentAccountNumber: 0,
},
schedule: {
date: null,
startTime: null,
endTime: null,
routeCode: null,
jobMaxMinutes: null,
jobMinMinutes: null,
},
referralNumber: null,
referralSequenceNumber: null,
referralDate: null,
referralCorrelationId: null,
eon: null,
};
const { wrapper } = setupMocks({});
// Act
const isValid = wrapper.vm.arePagePrerequisitesValid();
// Assert
expect(isValid).toBe(false);
});
describe("Damage requirements", () => {
test("Accepts null glassToReplace when is repair", () => {
// Arrange
store.getters.order.damage.isRepair = true;
store.getters.order.damage.glassToReplace = null;
store.getters.order.damage.numberOfChips = 1;
const { wrapper } = setupMocks({});
// Act
const isValid = wrapper.vm.arePagePrerequisitesValid();
// Assert
expect(isValid).toBe(true);
});
test("Rejects 0 chips when repair", () => {
// Arrange
store.getters.order.damage.isRepair = true;
store.getters.order.damage.numberOfChips = 0;
const { wrapper } = setupMocks({});
// Act
const isValid = wrapper.vm.arePagePrerequisitesValid();
// Assert
expect(isValid).toBe(false);
});
test("Rejects null chips when repair", () => {
// Arrange
store.getters.order.damage.isRepair = true;
store.getters.order.damage.numberOfChips = null;
const { wrapper } = setupMocks({});
// Act
const isValid = wrapper.vm.arePagePrerequisitesValid();
// Assert
expect(isValid).toBe(false);
});
test("Accepts null chips when not repair", () => {
// Arrange
store.getters.order.damage.isRepair = false;
store.getters.order.damage.numberOfChips = null;
const { wrapper } = setupMocks({});
// Act
const isValid = wrapper.vm.arePagePrerequisitesValid();
// Assert
expect(isValid).toBe(true);
});
test("Rejects empty glassToReplace when not repair", () => {
// Arrange
store.getters.order.damage.isRepair = false;
store.getters.order.damage.glassToReplace = null;
const { wrapper } = setupMocks({});
// Act
const isValid = wrapper.vm.arePagePrerequisitesValid();
// Assert
expect(isValid).toBe(false);
});
test("Rejects null glassToReplace when not repair", () => {
// Arrange
store.getters.order.damage.isRepair = false;
store.getters.order.damage.glassToReplace = [];
const { wrapper } = setupMocks({});
// Act
const isValid = wrapper.vm.arePagePrerequisitesValid();
// Assert
expect(isValid).toBe(false);
});
});
describe("Package requirements", () => {
test("Accepts null glassParts when is repair", () => {
// Arrange
store.getters.order.damage.isRepair = true;
store.getters.order.lineItems.glassParts = null;
const { wrapper } = setupMocks({});
// Act
const isValid = wrapper.vm.arePagePrerequisitesValid();
// Assert
expect(isValid).toBe(true);
});
test("Rejects null glassParts when not repair", () => {
// Arrange
store.getters.order.damage.isRepair = false;
store.getters.order.lineItems.glassParts = null;
const { wrapper } = setupMocks({});
// Act
const isValid = wrapper.vm.arePagePrerequisitesValid();
// Assert
expect(isValid).toBe(false);
});
});
describe("Service Location requirements", () => {
test("Accepts null provider address when mobile appointment", () => {
// Arrange
store.getters.order.serviceLocation.appointmentType = "Mobile";
store.getters.order.serviceLocation.provider.address = {};
const { wrapper } = setupMocks({});
// Act
const isValid = wrapper.vm.arePagePrerequisitesValid();
// Assert
expect(isValid).toBe(true);
});
test("Reject null provider address when non-mobile appointment", () => {
// Arrange
store.getters.order.serviceLocation.appointmentType = "Inshop";
store.getters.order.serviceLocation.provider.address = {};
const { wrapper } = setupMocks({});
// Act
const isValid = wrapper.vm.arePagePrerequisitesValid();
// Assert
expect(isValid).toBe(false);
});
test("Accepts null service location address when non-mobile appointment", () => {
// Arrange
store.getters.order.serviceLocation.appointmentType = "Inshop";
store.getters.order.serviceLocation.address = null;
store.getters.order.serviceLocation.address2 = null;
store.getters.order.serviceLocation.zipCode = null;
store.getters.order.serviceLocation.city = null;
store.getters.order.serviceLocation.state = null;
const { wrapper } = setupMocks({});
// Act
const isValid = wrapper.vm.arePagePrerequisitesValid();
// Assert
expect(isValid).toBe(true);
});
test("Rejects null service location address when mobile appointment", () => {
// Arrange
store.getters.order.serviceLocation.appointmentType = "Mobile";
store.getters.order.serviceLocation.address = null;
store.getters.order.serviceLocation.address2 = null;
store.getters.order.serviceLocation.zipCode = null;
store.getters.order.serviceLocation.city = null;
store.getters.order.serviceLocation.state = null;
const { wrapper } = setupMocks({});
// Act
const isValid = wrapper.vm.arePagePrerequisitesValid();
// Assert
expect(isValid).toBe(false);
});
});
});
});
function setupMocks(customMountOptions) {
customMountOptions.store = store;
const mountOptions = getMountOptions(customMountOptions);
const mockMixin = {
methods: {
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
return `${widgetName} ${cmsFieldName}`;
}),
},
};
mountOptions.global.mixins = [mockMixin];
const wrapper = shallowMount(review, mountOptions);
wrapper.vm.setCmsContent = jest.fn();
return { wrapper };
}

View file

@ -89,10 +89,6 @@
<hr class="my-0" /> <hr class="my-0" />
</div> </div>
<customerDetailsModalQuestion
ref="customerDetailsModalQuestion"
:previousCustomerValues="customerInfo" />
<funnelFooter <funnelFooter
cmsWidgetName="FunnelFooterWidget" cmsWidgetName="FunnelFooterWidget"
@back-clicked="backButtonAction" @back-clicked="backButtonAction"
@ -115,10 +111,11 @@ import serviceLocationReview from "@/layouts/review/review-sections/service-loca
import scheduleReview from "@/layouts/review/review-sections/schedule-review/schedule-review"; import scheduleReview from "@/layouts/review/review-sections/schedule-review/schedule-review";
import customerReview from "@/layouts/review/review-sections/customer-review/customer-review"; import customerReview from "@/layouts/review/review-sections/customer-review/customer-review";
import customerDetailsModalQuestion from "@/layouts/review/review-sections/customer-review/customer-details-modal-question/customer-details-modal-question.vue"; import { AppointmentTypeStrings } from "@/constants/schedule-constants";
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 store from "@/store";
export default { export default {
name: "review", name: "review",
@ -153,7 +150,75 @@ export default {
}, },
methods: { methods: {
arePagePrerequisitesValid() { arePagePrerequisitesValid() {
return true; // Vehicle
const vehicle = store.getters.order.vehicle;
const vehicleReqs = !!(vehicle.year && vehicle.make && vehicle.model && vehicle.style);
// Damage
const damage = store.getters.order.damage;
const damageReqs = !!(
(damage.isRepair && damage.numberOfChips) ||
(!damage.isRepair && damage.glassToReplace?.length)
);
// Service Package
const lineItems = store.getters.order.lineItems;
// damageReqs handles checking for damage, even though it is also required for this section.
const packageReqs = !!(
(damage.isRepair || lineItems.glassParts) &&
lineItems.supportingItems &&
lineItems.vaps
);
// Service Location
const serviceLocation = store.getters.order.serviceLocation;
const mobileReqs = !!(
serviceLocation.address &&
serviceLocation.city &&
serviceLocation.state &&
serviceLocation.zipCode
);
const providerLocation = serviceLocation.provider.address;
const dropOffInshopReqs = !!(
providerLocation.streetAddress &&
providerLocation.city &&
providerLocation.state &&
providerLocation.zipCode
);
const isMobile = serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE;
const serviceLocationReqs =
(isMobile && mobileReqs) || (!isMobile && dropOffInshopReqs);
// Schedule
const schedule = store.getters.order.schedule;
const scheduleReqs = !!(
schedule.date &&
schedule.startTime &&
schedule.endTime &&
schedule.jobMaxMinutes &&
schedule.jobMinMinutes
);
// Customer
const customer = store.getters.order.customer;
const customerReqs = !!(
customer.firstName &&
customer.lastName &&
customer.phoneNumber &&
customer.emailAddress
);
return (
vehicleReqs &&
damageReqs &&
packageReqs &&
serviceLocationReqs &&
scheduleReqs &&
customerReqs
);
}, },
backButtonAction() { backButtonAction() {
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route); this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
@ -190,7 +255,10 @@ export default {
); );
}, },
editCustomerDetails() { editCustomerDetails() {
this.$refs.customerDetailsModalQuestion.openModal(); this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_CUSTOMER_EDIT,
this.$route
);
}, },
}, },
computed: { computed: {
@ -234,7 +302,6 @@ export default {
serviceLocationReview, serviceLocationReview,
scheduleReview, scheduleReview,
customerReview, customerReview,
customerDetailsModalQuestion,
}, },
}; };
</script> </script>

View file

@ -6,7 +6,6 @@ import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { getMountOptions } from "@/helpers/unit-test-helper.js";
import store from "@/store"; import store from "@/store";
import router from "@/router"; import router from "@/router";
import { nextTick } from "vue";
import baseMixin from "../../mixins/base-mixin"; import baseMixin from "../../mixins/base-mixin";
// Mock basemixin // Mock basemixin
@ -141,6 +140,7 @@ describe("schedule.vue...", () => {
// Assert // Assert
expect(arePagePrerequisitesValid).toBe(true); expect(arePagePrerequisitesValid).toBe(true);
}); });
test("should pass arePagePrerequisitesValid with a inshop order and providerNumber", () => { test("should pass arePagePrerequisitesValid with a inshop order and providerNumber", () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -151,6 +151,7 @@ describe("schedule.vue...", () => {
// Assert // Assert
expect(arePagePrerequisitesValid).toBe(true); expect(arePagePrerequisitesValid).toBe(true);
}); });
test("should fail arePagePrerequisitesValid with a replace with no glass parts", async () => { test("should fail arePagePrerequisitesValid with a replace with no glass parts", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -162,6 +163,7 @@ describe("schedule.vue...", () => {
// Assert // Assert
expect(arePagePrerequisitesValid2).toBe(false); expect(arePagePrerequisitesValid2).toBe(false);
}); });
test("should fail arePagePrerequisitesValid without isInsurance", () => { test("should fail arePagePrerequisitesValid without isInsurance", () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -383,37 +385,29 @@ describe("schedule.vue...", () => {
expect(wrapper.vm.$refs.timeSlotModalQuestion.openModal).toBeCalled(); expect(wrapper.vm.$refs.timeSlotModalQuestion.openModal).toBeCalled();
}); });
test("getSelectedRouteCode should return schedule routeCode", () => {
//Arrange
const { wrapper } = setupMocks({});
wrapper.vm.selectableDatesData = {
days: [],
};
//Act
const testValue = wrapper.vm.getSelectedRouteCode();
//Assert
expect(testValue).toStrictEqual("000");
});
test("timeSlotModalClosed should null any selected date when there's no route code", () => { test("timeSlotModalClosed should null any selected date when there's no route code", () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.selectableDatesData = { wrapper.vm.selectableDatesData = {
days: [], days: [],
}; };
wrapper.setData({ wrapper.setData({
selectedDate: "1980-05-05", selectedDate: "1980-05-05",
}); });
wrapper.setData({
selectedTimeSlot: { const selectedTimeSlotInfo = {
timeSlot: {
date: "2019-01-01", date: "2019-01-01",
startTime: "09:00", startTime: "09:00",
endTime: "10:00", endTime: "10:00",
routeCode: null, routeCode: null,
}, },
isPremiumAppointment: null,
};
wrapper.setData({
selectedTimeSlotInfo: selectedTimeSlotInfo,
}); });
// Act // Act

View file

@ -24,17 +24,17 @@
<timeSlotModalQuestion <timeSlotModalQuestion
ref="timeSlotModalQuestion" ref="timeSlotModalQuestion"
customComponentId="timeSlotModalQuestion" customComponentId="timeSlotModalQuestion"
v-model="selectedTimeSlotInfo"
cmsWidgetName="TimeSlotModalQuestion" cmsWidgetName="TimeSlotModalQuestion"
mobilePremiumCmsWidgetName="MobilePremiumTimeSlotModal" mobilePremiumCmsWidgetName="MobilePremiumTimeSlotModal"
mobileCmsWidgetName="MobileTimeSlotModal" mobileCmsWidgetName="MobileTimeSlotModal"
dropoffCmsWidgetName="DropOffTimeSlotModal" dropoffCmsWidgetName="DropOffTimeSlotModal"
sameDayDropOffCmsWidgetName="SameDayDropOffTimeSlotModal" sameDayDropOffCmsWidgetName="SameDayDropOffTimeSlotModal"
overnightDropOffCmsWidgetName="OvernightDropOffTimeSlotModal" overnightDropOffCmsWidgetName="OvernightDropOffTimeSlotModal"
v-model="selectedTimeSlot"
:selectedDate="selectedDate" :selectedDate="selectedDate"
:appointmentType="appointmentType" :appointmentType="appointmentType"
:premiumAppointmentFee="mobilePremiumAppointmentFee" :premiumAppointmentFee="mobilePremiumAppointmentFee"
:dateAndTimeSlotData="timeSlotsForSelectedDate" :timeSlotsForSelectedDate="timeSlotsForSelectedDate"
:estimatedServiceMinutesMinimum="selectableDatesData.estimatedServiceMinutesMinimum" :estimatedServiceMinutesMinimum="selectableDatesData.estimatedServiceMinutesMinimum"
:estimatedServiceMinutesMaximum="selectableDatesData.estimatedServiceMinutesMaximum" :estimatedServiceMinutesMaximum="selectableDatesData.estimatedServiceMinutesMaximum"
@time-slot-modal-closed="timeSlotModalClosed" @time-slot-modal-closed="timeSlotModalClosed"
@ -81,12 +81,7 @@ import store from "@/store";
// DEFINE VALIDATION RULES // DEFINE VALIDATION RULES
defineRule("date-required", required(errorMessages.DATE_REQUIRED)); defineRule("date-required", required(errorMessages.DATE_REQUIRED));
defineRule("time-slot-selection-required", (value) => { defineRule("time-slot-selection-required", (value) => {
if ( if (value?.timeSlot?.routeCode == null) {
value.date == null ||
value.startTime == null ||
value.routeCode == null ||
value.estimatedServiceMinutesMaximum
) {
return errorMessages.DATE_REQUIRED; return errorMessages.DATE_REQUIRED;
} }
return true; return true;
@ -190,7 +185,7 @@ export default {
data() { data() {
return { return {
selectedDate: this.getSelectedDate(), selectedDate: this.getSelectedDate(),
selectedTimeSlot: this.getSelectedTimeSlot(), selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
selectableDatesData: [], selectableDatesData: [],
mobilePremiumAppointmentFee: null, mobilePremiumAppointmentFee: null,
}; };
@ -265,7 +260,7 @@ export default {
vm.mobilePremiumAppointmentFee = resultMap.premiumFeeWithPrice vm.mobilePremiumAppointmentFee = resultMap.premiumFeeWithPrice
? resultMap.premiumFeeWithPrice[0] ? resultMap.premiumFeeWithPrice[0]
: null; : null;
vm.updateFooterButtonText(vm.selectedTimeSlot); vm.updateFooterButtonText(vm.selectedTimeSlotInfo);
}); });
}, },
computed: { computed: {
@ -293,18 +288,22 @@ export default {
splitCopyOnCMSPlaceHolder, splitCopyOnCMSPlaceHolder,
arePagePrerequisitesValid() { arePagePrerequisitesValid() {
const serviceLocation = store.getters.order.serviceLocation; const serviceLocation = store.getters.order.serviceLocation;
const serviceLocationPreReqs = const serviceLocationPreReqs =
serviceLocation.zipCode && serviceLocation.zipCode &&
serviceLocation.zipCodeCtu && serviceLocation.zipCodeCtu &&
serviceLocation.appointmentType && serviceLocation.appointmentType &&
(serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE || (serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE ||
serviceLocation.provider.providerNumber); serviceLocation.provider.providerNumber);
const paymentInfo = store.getters.payment.isInsurance !== null; const paymentInfo = store.getters.payment.isInsurance !== null;
const supportingItems = store.getters.lineItems.supportingItems !== null; const supportingItems = store.getters.lineItems.supportingItems !== null;
const damageInfo = const damageInfo =
store.getters.order.damage.isRepair || store.getters.order.damage.isRepair ||
(store.getters.order.lineItems?.glassParts != null && (store.getters.order.lineItems?.glassParts != null &&
store.getters.order.lineItems.glassParts.length > 0); store.getters.order.lineItems.glassParts.length > 0);
return serviceLocationPreReqs && paymentInfo && supportingItems && damageInfo; return serviceLocationPreReqs && paymentInfo && supportingItems && damageInfo;
}, },
async getAvailableDatesMethod(startDate, endDate) { async getAvailableDatesMethod(startDate, endDate) {
@ -327,45 +326,55 @@ export default {
openInshopTimeSlotsModal() { openInshopTimeSlotsModal() {
this.$refs["timeSlotModalQuestion"].openModal(); this.$refs["timeSlotModalQuestion"].openModal();
}, },
getSelectedTimeSlot() {
return store.getters.order.schedule;
},
getSelectedDate() { getSelectedDate() {
return store.getters.order.schedule.date; return store.getters.order.schedule.date;
}, },
getSelectedRouteCode() { getSelectedTimeSlotInfo() {
return store.getters.order.schedule.routeCode; const supportingItems = this.getSupportingItems();
const isPremiumAppointment =
!!supportingItems.filter((lineItem) => lineItem.partType === PREMIUM_FEE_PART_TYPE)
.length > 0;
const selectedTimeSlotInfo = {
timeSlot: store.getters.order.schedule,
isPremiumAppointment: isPremiumAppointment,
};
return selectedTimeSlotInfo;
}, },
getSupportingItems() { getSupportingItems() {
return store.getters.lineItems.supportingItems; return store.getters.lineItems.supportingItems;
}, },
timeSlotModalClosed() { timeSlotModalClosed() {
// Clear the selectedDate if no timeSlot has been selected // Clear the selectedDate if no timeSlot has been selected
if (this.selectedTimeSlot.routeCode == null) { if (this.selectedTimeSlotInfo.timeSlot.routeCode == null) {
this.selectedDate = null; this.selectedDate = null;
} }
}, },
updateFooterButtonText(timeSlot) { updateFooterButtonText(timeSlotInfo) {
let funnelFooterButtonText; let funnelFooterButtonText;
if (!timeSlot || !timeSlot.date) { if (!timeSlotInfo || !timeSlotInfo.timeSlot.date) {
funnelFooterButtonText = "Continue"; funnelFooterButtonText = "Continue";
} else { } else {
funnelFooterButtonText = `Select ${this.convertSelectedDateToShortMonthAndDay( funnelFooterButtonText = `Select ${this.convertSelectedDateToShortMonthAndDay(
timeSlot.date timeSlotInfo.timeSlot.date
)}`; )}`;
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) { if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
funnelFooterButtonText += ` at ${this.getDisplayTextForMilitaryTime( funnelFooterButtonText += ` at ${this.getDisplayTextForMilitaryTime(
timeSlot.startTime timeSlotInfo.timeSlot.startTime
)}`; )}`;
} else if ( } else if (
this.appointmentType === AppointmentTypeStrings.MOBILE && this.appointmentType === AppointmentTypeStrings.MOBILE &&
!timeSlot.isPremiumAppointment !timeSlotInfo.isPremiumAppointment
) { ) {
funnelFooterButtonText += ` at ${this.getDisplayTextForMilitaryTime( funnelFooterButtonText += ` at ${this.getDisplayTextForMilitaryTime(
timeSlot.startTime, timeSlotInfo.timeSlot.startTime,
true true
)} - ${this.getDisplayTextForMilitaryTime(timeSlot.endTime, true)}`; )} - ${this.getDisplayTextForMilitaryTime(
timeSlotInfo.timeSlot.endTime,
true
)}`;
} }
} }
this.$refs.funnelFooter.updateButtonText(funnelFooterButtonText); this.$refs.funnelFooter.updateButtonText(funnelFooterButtonText);
@ -376,14 +385,16 @@ export default {
// Ex: April 25 // Ex: April 25
return dateObject.toLocaleDateString("en-us", { month: "short", day: "numeric" }); return dateObject.toLocaleDateString("en-us", { month: "short", day: "numeric" });
}, },
// Expected input: "HH:MM"
getDisplayTextForMilitaryTime(militaryTimeInput, shouldTrimMinutesIfEmpty = false) { getDisplayTextForMilitaryTime(militaryTimeInput, shouldTrimMinutesIfEmpty = false) {
// Expected input: "HH:MM"
let hours = parseInt(militaryTimeInput.split(":")[0]); let hours = parseInt(militaryTimeInput.split(":")[0]);
const minutes = militaryTimeInput.split(":")[1]; const minutes = militaryTimeInput.split(":")[1];
const meridianNotation = hours > 11 ? "PM" : "AM"; const meridianNotation = hours > 11 ? "PM" : "AM";
if (hours > 12) { if (hours > 12) {
hours -= 12; hours -= 12;
} }
if (shouldTrimMinutesIfEmpty && minutes === "00") { if (shouldTrimMinutesIfEmpty && minutes === "00") {
return `${hours} ${meridianNotation}`; return `${hours} ${meridianNotation}`;
} else { } else {
@ -395,7 +406,12 @@ export default {
}, },
forwardButtonAction() { forwardButtonAction() {
this.updateSupportingItems(); this.updateSupportingItems();
this.dispatchStoreAction(this.storeActions.SAVE_SCHEDULE, this.selectedTimeSlot, false);
this.dispatchStoreAction(
this.storeActions.SAVE_SCHEDULE,
this.selectedTimeSlotInfo.timeSlot,
false
);
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route); this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
}, },
@ -405,7 +421,7 @@ export default {
// if we have a premium fee(early bird), then save/update supporting items // if we have a premium fee(early bird), then save/update supporting items
if ( if (
this.appointmentType === AppointmentTypeStrings.MOBILE && this.appointmentType === AppointmentTypeStrings.MOBILE &&
this.selectedTimeSlot?.isPremiumAppointment this.selectedTimeSlotInfo?.isPremiumAppointment
) { ) {
const earlyBirdIndex = supportingItems.findIndex( const earlyBirdIndex = supportingItems.findIndex(
(item) => item.partType == PREMIUM_FEE_PART_TYPE (item) => item.partType == PREMIUM_FEE_PART_TYPE
@ -448,17 +464,20 @@ export default {
selectedDate(newValue, oldValue) { selectedDate(newValue, oldValue) {
// Clear time slot selection if date selected changes // Clear time slot selection if date selected changes
if (newValue !== oldValue) { if (newValue !== oldValue) {
this.selectedTimeSlot = { this.selectedTimeSlotInfo = {
timeSlot: {
date: null, date: null,
routeCode: null, routeCode: null,
startTime: null, startTime: null,
endTime: null, endTime: null,
jobMaxMinutes: null, jobMaxMinutes: null,
jobMinMinutes: null, jobMinMinutes: null,
},
isPremiumAppointment: null,
}; };
} }
}, },
selectedTimeSlot(newValue) { selectedTimeSlotInfo(newValue) {
this.updateFooterButtonText(newValue); this.updateFooterButtonText(newValue);
}, },
}, },

View file

@ -2,31 +2,77 @@ import { shallowMount, flushPromises } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { getMountOptions } from "@/helpers/unit-test-helper.js";
import timeSlotModalQuestion from "@/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue"; import timeSlotModalQuestion from "@/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue";
import store from "@/store"; import store from "@/store";
import { import {
RouteCodeFlags, RouteCodeFlags,
PREMIUM_TIME_SLOT_ID_FLAG, PREMIUM_TIME_SLOT_ID_FLAG,
PREMIUM_FEE_PART_TYPE, PREMIUM_FEE_PART_TYPE,
} from "@/constants/schedule-constants"; } from "@/constants/schedule-constants";
describe("time-slot-modal-list-button-question.vue", () => { describe("time-slot-modal-question.vue", () => {
const timeSlotsForSelectedDate = {
date: "2023-09-02",
timeSlots: [
{
id: "03335-01820-S-B*20334*8 AM",
startTime: "08:00",
endTime: "09:00",
offerPremium: false,
},
{
id: "03335-01820-S-B*20334*830 AM",
startTime: "08:30",
endTime: "09:30",
offerPremium: false,
},
{
id: "03335-01820-S-B*20334*9 AM",
startTime: "09:00",
endTime: "10:00",
offerPremium: false,
},
{
id: "03335-01820-S-B*20334*930 AM",
startTime: "09:30",
endTime: "10:30",
offerPremium: false,
},
{
id: "03335-01820-S-B*20334*10 AM",
startTime: "10:00",
endTime: "11:00",
offerPremium: false,
},
{
id: "03335-01820-S-B*20334*1030 AM",
startTime: "10:30",
endTime: "11:30",
offerPremium: false,
},
],
};
test("When selected time slot is changed, a correctly formatted selectedTimeSlot should be emitted", async () => { test("When selected time slot is changed, a correctly formatted selectedTimeSlot should be emitted", async () => {
//Arrange //Arrange
const date = "2023-07-01"; const date = "2023-09-02";
const timeSlotId = "testRouteCodeId"; const routeCode = "03335-01820-S-B*20334*830 AM";
const startTime = "8:00 AM"; const startTime = "08:30";
const endTime = "8:30 AM"; const endTime = "09:30";
const jobMaxMinutes = 100; const jobMaxMinutes = 100;
const jobMinMinutes = 50; const jobMinMinutes = 50;
const expectedEmit = [ const expectedEmit = [
[ [
{ {
timeSlot: {
date: date, date: date,
routeCode: timeSlotId, routeCode: routeCode,
startTime: startTime, startTime: startTime,
endTime: endTime, endTime: endTime,
jobMaxMinutes: jobMaxMinutes.toString(), jobMaxMinutes: jobMaxMinutes.toString(),
jobMinMinutes: jobMinMinutes.toString(), jobMinMinutes: jobMinMinutes.toString(),
}, },
isPremiumAppointment: false,
},
], ],
]; ];
@ -35,22 +81,16 @@ describe("time-slot-modal-list-button-question.vue", () => {
propsData: { propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes, estimatedServiceMinutesMaximum: jobMaxMinutes,
estimatedServiceMinutesMinimum: jobMinMinutes, estimatedServiceMinutesMinimum: jobMinMinutes,
dateAndTimeSlotData: { timeSlotsForSelectedDate: timeSlotsForSelectedDate,
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
},
],
},
}, },
}, },
}); });
wrapper.vm.$refs.timeSlots.closeModal = jest.fn(); wrapper.vm.$refs.timeSlots.closeModal = jest.fn();
wrapper.vm.selectedTimeSlotId = timeSlotId;
await wrapper.setData({
selectedRouteCode: routeCode,
});
//Act //Act
wrapper.vm.setSelectedTimeSlot(); wrapper.vm.setSelectedTimeSlot();
@ -59,7 +99,8 @@ describe("time-slot-modal-list-button-question.vue", () => {
expect(wrapper.emitted()["update:modelValue"]).toEqual(expectedEmit); expect(wrapper.emitted()["update:modelValue"]).toEqual(expectedEmit);
}); });
}); });
describe("time-slot-modal-list-button-question.vue Supplemental information", () => {
describe("time-slot-modal-question.vue Supplemental information", () => {
test("The correct supplemental information should be obtained from CMS - Mobile - Not Premium ", async () => { test("The correct supplemental information should be obtained from CMS - Mobile - Not Premium ", async () => {
//Arrange //Arrange
const mobileCmsWidgetName = "MobileNotPremium"; const mobileCmsWidgetName = "MobileNotPremium";
@ -72,11 +113,13 @@ describe("time-slot-modal-list-button-question.vue Supplemental information", ()
}, },
}, },
}); });
//Assert //Assert
expect(wrapper.vm.supplementalInformationBlock).toEqual( expect(wrapper.vm.supplementalInformationBlock).toEqual(
mockCmsContent[mobileCmsWidgetName]["BodyText"] mockCmsContent[mobileCmsWidgetName]["BodyText"]
); );
}); });
test("The correct supplemental information should be obtained from CMS - Mobile - Premium", async () => { test("The correct supplemental information should be obtained from CMS - Mobile - Premium", async () => {
//Arrange //Arrange
const mobileCmsWidgetName = "MobilePremium"; const mobileCmsWidgetName = "MobilePremium";
@ -89,11 +132,13 @@ describe("time-slot-modal-list-button-question.vue Supplemental information", ()
}, },
}, },
}); });
//Assert //Assert
expect(wrapper.vm.supplementalInformationBlock).toEqual( expect(wrapper.vm.supplementalInformationBlock).toEqual(
mockCmsContent[mobileCmsWidgetName]["BodyText"] mockCmsContent[mobileCmsWidgetName]["BodyText"]
); );
}); });
test("No supplemental information should be obtained from CMS - Inshop", async () => { test("No supplemental information should be obtained from CMS - Inshop", async () => {
//Arrange //Arrange
const inshopWidgetName = "Inshop"; const inshopWidgetName = "Inshop";
@ -109,6 +154,7 @@ describe("time-slot-modal-list-button-question.vue Supplemental information", ()
//Assert //Assert
expect(wrapper.vm.supplementalInformationBlock).toBeNull(); expect(wrapper.vm.supplementalInformationBlock).toBeNull();
}); });
test("No supplemental information should be obtained from CMS - Drop off, nothing selected", async () => { test("No supplemental information should be obtained from CMS - Drop off, nothing selected", async () => {
//Arrange //Arrange
const dropOffWidgetName = "Dropoff"; const dropOffWidgetName = "Dropoff";
@ -124,40 +170,69 @@ describe("time-slot-modal-list-button-question.vue Supplemental information", ()
//Assert //Assert
expect(wrapper.vm.supplementalInformationBlock).toBeNull(); expect(wrapper.vm.supplementalInformationBlock).toBeNull();
}); });
test("Correct supplemental information should be retrieved, Dropoff regular", async () => { test("Correct supplemental information should be retrieved, Dropoff regular", async () => {
//Arrange //Arrange
const dropOffWidgetName = "Dropoff"; const dropOffWidgetName = "Dropoff";
const modelValue = {
timeSlot: {
routeCode: `test${RouteCodeFlags.ALL_DAY_DROP_OFF}`,
date: null,
startTime: null,
endTime: null,
jobMaxMinutes: null,
jobMinMinutes: null,
},
isPremiumAppointment: null,
};
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
customMountOptions: { customMountOptions: {
propsData: { propsData: {
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF }, modelValue: modelValue,
appointmentType: "Dropoff", appointmentType: "Dropoff",
dropoffCmsWidgetName: dropOffWidgetName, dropoffCmsWidgetName: dropOffWidgetName,
}, },
}, },
}); });
//Assert //Assert
expect(wrapper.vm.supplementalInformationBlock).toEqual( expect(wrapper.vm.supplementalInformationBlock).toEqual(
mockCmsContent[dropOffWidgetName]["BodyText"] mockCmsContent[dropOffWidgetName]["BodyText"]
); );
}); });
test("Correct supplemental information should be retrieved, Dropoff Overnight", async () => { test("Correct supplemental information should be retrieved, Dropoff Overnight", async () => {
//Arrange //Arrange
const overnightDropOffCmsWidgetName = "Overnight"; const overnightDropOffCmsWidgetName = "Overnight";
const modelValue = {
timeSlot: {
routeCode: `test${RouteCodeFlags.OVERNIGHT_DROP_OFF}`,
date: null,
startTime: null,
endTime: null,
jobMaxMinutes: null,
jobMinMinutes: null,
},
isPremiumAppointment: null,
};
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
customMountOptions: { customMountOptions: {
propsData: { propsData: {
modelValue: { routeCode: "Test" + RouteCodeFlags.OVERNIGHT_DROP_OFF }, modelValue: modelValue,
appointmentType: "Dropoff", appointmentType: "Dropoff",
overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName, overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName,
}, },
}, },
}); });
//Assert //Assert
expect(wrapper.vm.supplementalInformationBlock).toEqual( expect(wrapper.vm.supplementalInformationBlock).toEqual(
mockCmsContent[overnightDropOffCmsWidgetName]["BodyText"] mockCmsContent[overnightDropOffCmsWidgetName]["BodyText"]
); );
}); });
test("Correct supplemental information should be retrieved, Dropoff Sameday", async () => { test("Correct supplemental information should be retrieved, Dropoff Sameday", async () => {
//Arrange //Arrange
const sameDayDropOffCmsWidgetName = "Sameday"; const sameDayDropOffCmsWidgetName = "Sameday";
@ -167,13 +242,24 @@ describe("time-slot-modal-list-button-question.vue Supplemental information", ()
const endTime = "8:30 AM"; const endTime = "8:30 AM";
const jobMaxMinutes = 100; const jobMaxMinutes = 100;
const jobMinMinutes = 50; const jobMinMinutes = 50;
const modelValue = {
timeSlot: {
routeCode: `test${RouteCodeFlags.ALL_DAY_DROP_OFF}`,
date: null,
startTime: null,
endTime: null,
jobMaxMinutes: null,
jobMinMinutes: null,
},
isPremiumAppointment: null,
};
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
customMountOptions: { customMountOptions: {
propsData: { propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes, estimatedServiceMinutesMaximum: jobMaxMinutes,
estimatedServiceMinutesMinimum: jobMinMinutes, estimatedServiceMinutesMinimum: jobMinMinutes,
dateAndTimeSlotData: { timeSlotsForSelectedDate: {
date: date, date: date,
timeSlots: [ timeSlots: [
{ {
@ -183,7 +269,7 @@ describe("time-slot-modal-list-button-question.vue Supplemental information", ()
}, },
], ],
}, },
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF }, modelValue: modelValue,
appointmentType: "Dropoff", appointmentType: "Dropoff",
sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName, sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName,
}, },
@ -208,12 +294,24 @@ describe("time-slot-modal-list-button-question.vue Disclaimer", () => {
const jobMaxMinutes = 100; const jobMaxMinutes = 100;
const jobMinMinutes = 50; const jobMinMinutes = 50;
const modelValue = {
timeSlot: {
routeCode: `Test${RouteCodeFlags.ALL_DAY_DROP_OFF}`,
date: null,
startTime: null,
endTime: null,
jobMaxMinutes: null,
jobMinMinutes: null,
},
isPremiumAppointment: null,
};
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
customMountOptions: { customMountOptions: {
propsData: { propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes, estimatedServiceMinutesMaximum: jobMaxMinutes,
estimatedServiceMinutesMinimum: jobMinMinutes, estimatedServiceMinutesMinimum: jobMinMinutes,
dateAndTimeSlotData: { timeSlotsForSelectedDate: {
date: date, date: date,
timeSlots: [ timeSlots: [
{ {
@ -223,7 +321,7 @@ describe("time-slot-modal-list-button-question.vue Disclaimer", () => {
}, },
], ],
}, },
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF }, modelValue: modelValue,
appointmentType: "Dropoff", appointmentType: "Dropoff",
sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName, sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName,
}, },
@ -235,47 +333,88 @@ describe("time-slot-modal-list-button-question.vue Disclaimer", () => {
mockCmsContent[sameDayDropOffCmsWidgetName]["FooterText"] mockCmsContent[sameDayDropOffCmsWidgetName]["FooterText"]
); );
}); });
test("Correct disclaimer information should be retrieved, Dropoff regular", async () => { test("Correct disclaimer information should be retrieved, Dropoff regular", async () => {
//Arrange //Arrange
const dropOffWidgetName = "Dropoff"; const dropOffWidgetName = "Dropoff";
const modelValue = {
timeSlot: {
routeCode: `Test${RouteCodeFlags.ALL_DAY_DROP_OFF}`,
date: null,
startTime: null,
endTime: null,
jobMaxMinutes: null,
jobMinMinutes: null,
},
isPremiumAppointment: null,
};
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
customMountOptions: { customMountOptions: {
propsData: { propsData: {
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF }, modelValue: modelValue,
appointmentType: "Dropoff", appointmentType: "Dropoff",
dropoffCmsWidgetName: dropOffWidgetName, dropoffCmsWidgetName: dropOffWidgetName,
}, },
}, },
}); });
//Assert //Assert
expect(wrapper.vm.disclaimerTextBlockCopy).toEqual( expect(wrapper.vm.disclaimerTextBlockCopy).toEqual(
mockCmsContent[dropOffWidgetName]["FooterText"] mockCmsContent[dropOffWidgetName]["FooterText"]
); );
}); });
test("Correct disclaimer information should be retrieved, Dropoff overnight", async () => { test("Correct disclaimer information should be retrieved, Dropoff overnight", async () => {
//Arrange //Arrange
const overnightDropOffCmsWidgetName = "Overnight"; const overnightDropOffCmsWidgetName = "Overnight";
const modelValue = {
timeSlot: {
routeCode: `Test${RouteCodeFlags.OVERNIGHT_DROP_OFF}`,
date: null,
startTime: null,
endTime: null,
jobMaxMinutes: null,
jobMinMinutes: null,
},
isPremiumAppointment: null,
};
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
customMountOptions: { customMountOptions: {
propsData: { propsData: {
modelValue: { routeCode: "Test" + RouteCodeFlags.OVERNIGHT_DROP_OFF }, modelValue: modelValue,
appointmentType: "Dropoff", appointmentType: "Dropoff",
overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName, overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName,
}, },
}, },
}); });
//Assert //Assert
expect(wrapper.vm.disclaimerTextBlockCopy).toEqual( expect(wrapper.vm.disclaimerTextBlockCopy).toEqual(
mockCmsContent[overnightDropOffCmsWidgetName]["FooterText"] mockCmsContent[overnightDropOffCmsWidgetName]["FooterText"]
); );
}); });
test("No disclaimer information should be retrieved, non-dropoff", async () => { test("No disclaimer information should be retrieved, non-dropoff", async () => {
//Arrange //Arrange
const inshopWidgetName = "Inshop"; const inshopWidgetName = "Inshop";
const modelValue = {
timeSlot: {
routeCode: `test`,
date: null,
startTime: null,
endTime: null,
jobMaxMinutes: null,
jobMinMinutes: null,
},
isPremiumAppointment: null,
};
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
customMountOptions: { customMountOptions: {
propsData: { propsData: {
modelValue: { routeCode: "test" }, modelValue: modelValue,
appointmentType: "Inshop", appointmentType: "Inshop",
cmsWidgetName: inshopWidgetName, cmsWidgetName: inshopWidgetName,
}, },
@ -286,7 +425,7 @@ describe("time-slot-modal-list-button-question.vue Disclaimer", () => {
}); });
}); });
describe("time-slot-modal-list-button-question.vue Duration", () => { describe("time-slot-modal-question.vue Duration", () => {
test("The correct duration information should be displayed - Dropoff - Sameday ", async () => { test("The correct duration information should be displayed - Dropoff - Sameday ", async () => {
//Arrange //Arrange
const sameDayDropOffCmsWidgetName = "Sameday"; const sameDayDropOffCmsWidgetName = "Sameday";
@ -297,12 +436,24 @@ describe("time-slot-modal-list-button-question.vue Duration", () => {
const jobMaxMinutes = 100; const jobMaxMinutes = 100;
const jobMinMinutes = 50; const jobMinMinutes = 50;
const modelValue = {
timeSlot: {
routeCode: `test` + RouteCodeFlags.ALL_DAY_DROP_OFF,
date: null,
startTime: null,
endTime: null,
jobMaxMinutes: null,
jobMinMinutes: null,
},
isPremiumAppointment: null,
};
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
customMountOptions: { customMountOptions: {
propsData: { propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes, estimatedServiceMinutesMaximum: jobMaxMinutes,
estimatedServiceMinutesMinimum: jobMinMinutes, estimatedServiceMinutesMinimum: jobMinMinutes,
dateAndTimeSlotData: { timeSlotsForSelectedDate: {
date: date, date: date,
timeSlots: [ timeSlots: [
{ {
@ -312,7 +463,7 @@ describe("time-slot-modal-list-button-question.vue Duration", () => {
}, },
], ],
}, },
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF }, modelValue: modelValue,
appointmentType: "Dropoff", appointmentType: "Dropoff",
sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName, sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName,
}, },
@ -324,30 +475,59 @@ describe("time-slot-modal-list-button-question.vue Duration", () => {
mockCmsContent[sameDayDropOffCmsWidgetName]["SubheaderText"] mockCmsContent[sameDayDropOffCmsWidgetName]["SubheaderText"]
); );
}); });
test("Correct duration information should be retrieved, Dropoff overnight", async () => { test("Correct duration information should be retrieved, Dropoff overnight", async () => {
//Arrange //Arrange
const overnightDropOffCmsWidgetName = "Overnight"; const overnightDropOffCmsWidgetName = "Overnight";
const modelValue = {
timeSlot: {
routeCode: `test` + RouteCodeFlags.OVERNIGHT_DROP_OFF,
date: null,
startTime: null,
endTime: null,
jobMaxMinutes: null,
jobMinMinutes: null,
},
isPremiumAppointment: null,
};
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
customMountOptions: { customMountOptions: {
propsData: { propsData: {
modelValue: { routeCode: "Test" + RouteCodeFlags.OVERNIGHT_DROP_OFF }, modelValue: modelValue,
appointmentType: "Dropoff", appointmentType: "Dropoff",
overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName, overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName,
}, },
}, },
}); });
//Assert //Assert
expect(wrapper.vm.durationTextBlockCopy).toEqual( expect(wrapper.vm.durationTextBlockCopy).toEqual(
mockCmsContent[overnightDropOffCmsWidgetName]["SubheaderText"] mockCmsContent[overnightDropOffCmsWidgetName]["SubheaderText"]
); );
}); });
test("Correct duration information should be retrieved, Dropoff", async () => { test("Correct duration information should be retrieved, Dropoff", async () => {
//Arrange //Arrange
const dropoffCmsWidgetName = "Dropoff"; const dropoffCmsWidgetName = "Dropoff";
const modelValue = {
timeSlot: {
routeCode: `test` + RouteCodeFlags.ALL_DAY_DROP_OFF,
date: null,
startTime: null,
endTime: null,
jobMaxMinutes: null,
jobMinMinutes: null,
},
isPremiumAppointment: null,
};
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
customMountOptions: { customMountOptions: {
propsData: { propsData: {
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF }, modelValue: modelValue,
appointmentType: "Dropoff", appointmentType: "Dropoff",
dropoffCmsWidgetName: dropoffCmsWidgetName, dropoffCmsWidgetName: dropoffCmsWidgetName,
}, },
@ -358,25 +538,39 @@ describe("time-slot-modal-list-button-question.vue Duration", () => {
mockCmsContent[dropoffCmsWidgetName]["SubheaderText"] mockCmsContent[dropoffCmsWidgetName]["SubheaderText"]
); );
}); });
test("Correct duration information should be retrieved, Inshop - convert to hours", async () => { test("Correct duration information should be retrieved, Inshop - convert to hours", async () => {
//Arrange //Arrange
const cmsWidgetName = "Inshop"; const cmsWidgetName = "Inshop";
const modelValue = {
timeSlot: {
routeCode: `test`,
date: null,
startTime: null,
endTime: null,
},
isPremiumAppointment: null,
};
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
customMountOptions: { customMountOptions: {
propsData: { propsData: {
estimatedServiceMinutesMaximum: 180, estimatedServiceMinutesMaximum: 180,
estimatedServiceMinutesMinimum: 120, estimatedServiceMinutesMinimum: 120,
modelValue: { routeCode: "Test" }, modelValue: modelValue,
appointmentType: "Inshop", appointmentType: "Inshop",
cmsWidgetName: cmsWidgetName, cmsWidgetName: cmsWidgetName,
}, },
}, },
}); });
//Assert //Assert
expect(wrapper.vm.durationTextBlockCopy).toEqual( expect(wrapper.vm.durationTextBlockCopy).toEqual(
mockCmsContent[cmsWidgetName]["SubheaderText"] + " 2 - 3 hours" mockCmsContent[cmsWidgetName]["SubheaderText"] + " 2 - 3 hours"
); );
}); });
test("Correct duration information should be retrieved, Inshop - convert to minutes", async () => { test("Correct duration information should be retrieved, Inshop - convert to minutes", async () => {
//Arrange //Arrange
const cmsWidgetName = "Inshop"; const cmsWidgetName = "Inshop";
@ -396,6 +590,7 @@ describe("time-slot-modal-list-button-question.vue Duration", () => {
mockCmsContent[cmsWidgetName]["SubheaderText"] + " 60 - 90 minutes" mockCmsContent[cmsWidgetName]["SubheaderText"] + " 60 - 90 minutes"
); );
}); });
test("Correct duration information should be retrieved, Inshop - min = maximum time", async () => { test("Correct duration information should be retrieved, Inshop - min = maximum time", async () => {
//Arrange //Arrange
const cmsWidgetName = "Inshop"; const cmsWidgetName = "Inshop";
@ -415,6 +610,7 @@ describe("time-slot-modal-list-button-question.vue Duration", () => {
mockCmsContent[cmsWidgetName]["SubheaderText"] + " 4 hours" mockCmsContent[cmsWidgetName]["SubheaderText"] + " 4 hours"
); );
}); });
test("No duration information should be displayed, Mobile", async () => { test("No duration information should be displayed, Mobile", async () => {
//Arrange //Arrange
const mobileCmsWidgetName = "Mobile"; const mobileCmsWidgetName = "Mobile";
@ -450,7 +646,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
propsData: { propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes, estimatedServiceMinutesMaximum: jobMaxMinutes,
estimatedServiceMinutesMinimum: jobMinMinutes, estimatedServiceMinutesMinimum: jobMinMinutes,
dateAndTimeSlotData: { timeSlotsForSelectedDate: {
date: date, date: date,
timeSlots: [ timeSlots: [
{ {
@ -473,6 +669,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
}; };
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject); expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
}); });
test("Available Timeslots should be formatted correctly - Dropoff - Overnight ", async () => { test("Available Timeslots should be formatted correctly - Dropoff - Overnight ", async () => {
//Arrange //Arrange
const overnightDropOffCmsWidgetName = "Overnight"; const overnightDropOffCmsWidgetName = "Overnight";
@ -488,7 +685,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
propsData: { propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes, estimatedServiceMinutesMaximum: jobMaxMinutes,
estimatedServiceMinutesMinimum: jobMinMinutes, estimatedServiceMinutesMinimum: jobMinMinutes,
dateAndTimeSlotData: { timeSlotsForSelectedDate: {
date: date, date: date,
timeSlots: [ timeSlots: [
{ {
@ -511,6 +708,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
}; };
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject); expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
}); });
test("Available Timeslots should be formatted correctly - Dropoff", async () => { test("Available Timeslots should be formatted correctly - Dropoff", async () => {
//Arrange //Arrange
const dropoffCmsWidgetName = "Dropoff"; const dropoffCmsWidgetName = "Dropoff";
@ -526,7 +724,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
propsData: { propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes, estimatedServiceMinutesMaximum: jobMaxMinutes,
estimatedServiceMinutesMinimum: jobMinMinutes, estimatedServiceMinutesMinimum: jobMinMinutes,
dateAndTimeSlotData: { timeSlotsForSelectedDate: {
date: date, date: date,
timeSlots: [ timeSlots: [
{ {
@ -549,6 +747,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
}; };
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject); expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
}); });
test("Available Timeslots should be formatted correctly - Mobile not premium", async () => { test("Available Timeslots should be formatted correctly - Mobile not premium", async () => {
//Arrange //Arrange
const mobileCmsWidgetName = "MobileNotPremium"; const mobileCmsWidgetName = "MobileNotPremium";
@ -564,7 +763,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
propsData: { propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes, estimatedServiceMinutesMaximum: jobMaxMinutes,
estimatedServiceMinutesMinimum: jobMinMinutes, estimatedServiceMinutesMinimum: jobMinMinutes,
dateAndTimeSlotData: { timeSlotsForSelectedDate: {
date: date, date: date,
timeSlots: [ timeSlots: [
{ {
@ -587,6 +786,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
}; };
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject); expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
}); });
test("Available Timeslots should be formatted correctly - Mobile with premium", async () => { test("Available Timeslots should be formatted correctly - Mobile with premium", async () => {
//Arrange //Arrange
const mobilePremiumCmsWidgetName = "MobilePremium"; const mobilePremiumCmsWidgetName = "MobilePremium";
@ -605,7 +805,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
premiumAppointmentFee: { premiumAppointmentFee: {
partType: PREMIUM_FEE_PART_TYPE, partType: PREMIUM_FEE_PART_TYPE,
}, },
dateAndTimeSlotData: { timeSlotsForSelectedDate: {
date: date, date: date,
timeSlots: [ timeSlots: [
{ {
@ -631,8 +831,10 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
buttonLabelSubCopy: "+$15.99", buttonLabelSubCopy: "+$15.99",
value: timeSlotId + PREMIUM_TIME_SLOT_ID_FLAG, value: timeSlotId + PREMIUM_TIME_SLOT_ID_FLAG,
}; };
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject); expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
}); });
test("Available Timeslots should be formatted correctly - Inshop", async () => { test("Available Timeslots should be formatted correctly - Inshop", async () => {
//Arrange //Arrange
const cmsWidgetName = "Inshop"; const cmsWidgetName = "Inshop";
@ -648,7 +850,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
propsData: { propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes, estimatedServiceMinutesMaximum: jobMaxMinutes,
estimatedServiceMinutesMinimum: jobMinMinutes, estimatedServiceMinutesMinimum: jobMinMinutes,
dateAndTimeSlotData: { timeSlotsForSelectedDate: {
date: date, date: date,
timeSlots: [ timeSlots: [
{ {
@ -678,7 +880,7 @@ const mockCmsContent = {
HeaderText: "Mobile, not premium HeaderText", HeaderText: "Mobile, not premium HeaderText",
}, },
MobilePremium: { MobilePremium: {
"BodyText:": "Mobile, with premium BodyText", BodyText: "Mobile, with premium BodyText",
FooterText: "Mobile, with premium FooterText", FooterText: "Mobile, with premium FooterText",
SubheaderText: "Mobile, with premium SubheaderText", SubheaderText: "Mobile, with premium SubheaderText",
HeaderText: "Mobile, with premium HeaderText", HeaderText: "Mobile, with premium HeaderText",

View file

@ -22,7 +22,7 @@
:answers="availableTimeSlots" :answers="availableTimeSlots"
groupName="chooseTimeSlot" groupName="chooseTimeSlot"
textPosition="text-center" textPosition="text-center"
v-model="selectedTimeSlotId" v-model="selectedRouteCode"
isRequired isRequired
validationRules="time-slot-required" /> validationRules="time-slot-required" />
<div <div
@ -47,12 +47,12 @@ import buttonQuestion from "@/digital-components/button-question/button-question
import timeSlotModalListButton from "./time-slot-modal-list-button/time-slot-modal-list-button"; import timeSlotModalListButton from "./time-slot-modal-list-button/time-slot-modal-list-button";
// Helpers // Helpers
import { deepClone } from "@/helpers/object-helper";
import { import {
convertDateStringToDate, convertDateStringToDate,
militaryToTwelveHourTime, militaryToTwelveHourTime,
getDisplayTextForDurationLength, getDisplayTextForDurationLength,
} from "@/layouts/schedule/helpers/schedule-helper"; } from "@/layouts/schedule/helpers/schedule-helper";
import { deepClone } from "@/helpers/object-helper";
// Validation - TODO: Move this somewhere more global? // Validation - TODO: Move this somewhere more global?
import { defineRule, useField } from "vee-validate"; import { defineRule, useField } from "vee-validate";
@ -87,12 +87,15 @@ export default {
modelValue: { modelValue: {
type: Object, type: Object,
default: () => ({ default: () => ({
timeSlot: {
routeCode: null, routeCode: null,
date: null, date: null,
startTime: null, startTime: null,
endTime: null, endTime: null,
jobMaxMinutes: null, jobMaxMinutes: null,
jobMinMinutes: null, jobMinMinutes: null,
},
isPremiumAppointment: null,
}), }),
}, },
cmsWidgetName: String, cmsWidgetName: String,
@ -102,7 +105,7 @@ export default {
sameDayDropOffCmsWidgetName: String, sameDayDropOffCmsWidgetName: String,
overnightDropOffCmsWidgetName: String, overnightDropOffCmsWidgetName: String,
appointmentType: String, appointmentType: String,
dateAndTimeSlotData: Object, timeSlotsForSelectedDate: Object,
premiumAppointmentFee: Object, premiumAppointmentFee: Object,
estimatedServiceMinutesMinimum: Number, estimatedServiceMinutesMinimum: Number,
estimatedServiceMinutesMaximum: Number, estimatedServiceMinutesMaximum: Number,
@ -113,7 +116,7 @@ export default {
data() { data() {
return { return {
isModalOpened: false, isModalOpened: false,
selectedValue: null, selectedRouteCode: this.getSelectedRouteCode(),
timeSlotModalListButton: timeSlotModalListButton, timeSlotModalListButton: timeSlotModalListButton,
}; };
}, },
@ -150,36 +153,31 @@ export default {
modalName() { modalName() {
return "timeSlots"; return "timeSlots";
}, },
selectedTimeSlotId: { modal() {
get: function () { return this.$refs[this.modalName];
return this.modelValue?.routeCode;
},
set: function (newValue) {
// Button Question only supports Number, or String data types so we must convert to the full object before emitting
this.selectedValue = this.getSelectedTimeSlotObject(newValue);
},
}, },
supplementalInformationBlock() { supplementalInformationBlock() {
let appointmentTypeCmsWidgetName; let appointmentTypeCmsWidgetName;
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) { if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
return null; return null;
} else if (this.appointmentType === AppointmentTypeStrings.MOBILE) { } else if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
appointmentTypeCmsWidgetName = this.selectedTimeSlotId?.includes( appointmentTypeCmsWidgetName = this.selectedRouteCode?.includes(
PREMIUM_TIME_SLOT_ID_FLAG PREMIUM_TIME_SLOT_ID_FLAG
) )
? this.mobilePremiumCmsWidgetName ? this.mobilePremiumCmsWidgetName
: this.mobileCmsWidgetName; : this.mobileCmsWidgetName;
} else { } else {
if (!this.selectedTimeSlotId) { if (!this.selectedRouteCode) {
return null; return null;
} else { } else {
appointmentTypeCmsWidgetName = appointmentTypeCmsWidgetName =
this.getRelevantDropOffCmsWidgetNameForSelectedTimeSlot( this.getRelevantDropOffCmsWidgetNameForSelectedTimeSlot(
this.selectedTimeSlotId, this.selectedRouteCode,
true true
); );
} }
} }
return this.getCmsContent( return this.getCmsContent(
appointmentTypeCmsWidgetName, appointmentTypeCmsWidgetName,
cmsWidgetFieldMappings.SUPPLEMENTAL_INFORMATION cmsWidgetFieldMappings.SUPPLEMENTAL_INFORMATION
@ -232,13 +230,13 @@ export default {
}, },
disclaimerTextBlockCopy() { disclaimerTextBlockCopy() {
if (this.appointmentType === AppointmentTypeStrings.DROP_OFF) { if (this.appointmentType === AppointmentTypeStrings.DROP_OFF) {
if (this.selectedTimeSlotId?.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) { if (this.selectedRouteCode?.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
if (this.isSameDay) { if (this.isSameDay) {
return this.sameDayDropOffDisclaimerText; return this.sameDayDropOffDisclaimerText;
} else { } else {
return this.dropoffDisclaimerText; return this.dropoffDisclaimerText;
} }
} else if (this.selectedTimeSlotId?.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) { } else if (this.selectedRouteCode?.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
return this.overnightDropOffDisclaimerText; return this.overnightDropOffDisclaimerText;
} else { } else {
return null; return null;
@ -281,9 +279,9 @@ export default {
} else if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) { } else if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
return this.inshopDurationText; return this.inshopDurationText;
} else { } else {
if (this.selectedTimeSlotId?.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) { if (this.selectedRouteCode?.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
return this.overnightDropoffDurationText; return this.overnightDropoffDurationText;
} else if (this.selectedTimeSlotId?.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) { } else if (this.selectedRouteCode?.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
if (this.isSameDay) { if (this.isSameDay) {
return this.sameDayDropoffDurationText; return this.sameDayDropoffDurationText;
} else { } else {
@ -295,21 +293,21 @@ export default {
} }
}, },
isSameDay() { isSameDay() {
if (!this.dateAndTimeSlotData) { if (!this.timeSlotsForSelectedDate) {
return false; return false;
} }
const selectedDate = this.dateAndTimeSlotData.date; const selectedDate = this.timeSlotsForSelectedDate.date;
const todaysDate = new Date().toISOString().split("T")[0]; const todaysDate = new Date().toISOString().split("T")[0];
return selectedDate === todaysDate; return selectedDate === todaysDate;
}, },
dateSelectedReadableDate() { dateSelectedReadableDate() {
if (!this.dateAndTimeSlotData) { if (!this.timeSlotsForSelectedDate) {
return null; return null;
} }
// This conversion ensures we don't get get GMT induced date changes // This conversion ensures we don't get get GMT induced date changes
const dateObject = convertDateStringToDate(this.dateAndTimeSlotData.date); const dateObject = convertDateStringToDate(this.timeSlotsForSelectedDate.date);
// Ex: Tuesday, April 22 // Ex: Tuesday, April 22
return dateObject.toLocaleDateString("en-us", { return dateObject.toLocaleDateString("en-us", {
weekday: "long", weekday: "long",
@ -318,21 +316,20 @@ export default {
}); });
}, },
availableTimeSlots() { availableTimeSlots() {
if (!this.dateAndTimeSlotData) { if (!this.timeSlotsForSelectedDate) {
return null; return null;
} }
if (this.appointmentType === AppointmentTypeStrings.DROP_OFF) { if (this.appointmentType === AppointmentTypeStrings.DROP_OFF) {
return this.getAvailableTimeSlotsForDropOff(this.dateAndTimeSlotData.timeSlots); return this.getAvailableTimeSlotsForDropOff(
this.timeSlotsForSelectedDate.timeSlots
);
} else if (this.appointmentType === AppointmentTypeStrings.MOBILE) { } else if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
return this.getAvailableTimeSlotsForMobile(this.dateAndTimeSlotData.timeSlots); return this.getAvailableTimeSlotsForMobile(this.timeSlotsForSelectedDate.timeSlots);
} else { } else {
return this.getAvailableTimeSlotsForInshop(this.dateAndTimeSlotData.timeSlots); return this.getAvailableTimeSlotsForInshop(this.timeSlotsForSelectedDate.timeSlots);
} }
}, },
modal() {
return this.$refs[this.modalName];
},
}, },
methods: { methods: {
openModal() { openModal() {
@ -348,16 +345,19 @@ export default {
this.$emit("time-slot-modal-closed"); this.$emit("time-slot-modal-closed");
}, },
async setSelectedTimeSlot() { async setSelectedTimeSlot() {
this.$emit("update:modelValue", this.selectedValue); this.$emit(
"update:modelValue",
this.getSelectedTimeSlotInfoObject(this.selectedRouteCode)
);
this.closeModal(); this.closeModal();
}, },
getRelevantDropOffCmsWidgetNameForSelectedTimeSlot( getRelevantDropOffCmsWidgetNameForSelectedTimeSlot(
selectedTimeSlotId, selectedRouteCode,
isSameDayRelevant = false isSameDayRelevant = false
) { ) {
if (selectedTimeSlotId.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) { if (selectedRouteCode.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
return this.overnightDropOffCmsWidgetName; return this.overnightDropOffCmsWidgetName;
} else if (selectedTimeSlotId.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) { } else if (selectedRouteCode.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
return this.isSameDay && isSameDayRelevant return this.isSameDay && isSameDayRelevant
? this.sameDayDropOffCmsWidgetName ? this.sameDayDropOffCmsWidgetName
: this.dropoffCmsWidgetName; : this.dropoffCmsWidgetName;
@ -416,8 +416,9 @@ export default {
getPremiumAppointmentTimeSlot(timeSlotData) { getPremiumAppointmentTimeSlot(timeSlotData) {
const formattedPrice = const formattedPrice =
"+$" + this.getTotalLineItemPrice(this.premiumAppointmentFee).toFixed(2); "+$" + this.getTotalLineItemPrice(this.premiumAppointmentFee).toFixed(2);
return { return {
// Unique value is required for each <input> and the premium appoinment shares a timeSlot ID // Unique value is required for each <input> and the premium appoinment shares an id
value: this.addPremiumFlagToInput(timeSlotData.id), value: this.addPremiumFlagToInput(timeSlotData.id),
buttonLabel: this.premiumAppointmentButtonText, buttonLabel: this.premiumAppointmentButtonText,
buttonLabelSubCopy: formattedPrice, buttonLabelSubCopy: formattedPrice,
@ -426,41 +427,68 @@ export default {
}, },
}; };
}, },
autoSelectTimeSlotIfOnlyOneIsAvailable(newAvailableTimeSlotsValue) { getSelectedRouteCode() {
const numberOfOptions = newAvailableTimeSlotsValue?.length; let selectedRouteCode;
if (!this.modelValue?.timeSlot) {
selectedRouteCode = null;
}
if (this.modelValue?.isPremiumAppointment) {
selectedRouteCode = this.addPremiumFlagToInput(
this.modelValue?.timeSlot?.routeCode
);
} else {
selectedRouteCode = this.modelValue?.timeSlot?.routeCode;
}
return selectedRouteCode;
},
autoSelectTimeSlotIfOnlyOneIsAvailable() {
const numberOfOptions = this.availableTimeSlots?.length;
if (numberOfOptions === 1) { if (numberOfOptions === 1) {
this.selectedTimeSlotId = newAvailableTimeSlotsValue[0].value; this.selectedRouteCode = this.availableTimeSlots[0].value;
} }
}, },
addPremiumFlagToInput(timeSlotId) { addPremiumFlagToInput(routeCode) {
return (timeSlotId += PREMIUM_TIME_SLOT_ID_FLAG); return (routeCode += PREMIUM_TIME_SLOT_ID_FLAG);
}, },
removePremiumFlagFromInput(timeSlotId) { removePremiumFlagFromInput(routeCode) {
return timeSlotId.substring(0, timeSlotId.length - PREMIUM_TIME_SLOT_ID_FLAG.length); return routeCode.replace(PREMIUM_TIME_SLOT_ID_FLAG, "");
}, },
getSelectedTimeSlotObject(timeSlotId) { getSelectedTimeSlotInfoObject(routeCode) {
const timeSlot = this.dateAndTimeSlotData?.timeSlots?.find( const routeCodeIncludesPremium = routeCode?.includes(PREMIUM_TIME_SLOT_ID_FLAG);
(timeSlot) => timeSlot.id == timeSlotId if (routeCodeIncludesPremium) {
routeCode = this.removePremiumFlagFromInput(routeCode);
}
const timeSlot = this.timeSlotsForSelectedDate?.timeSlots?.find(
(timeSlot) => timeSlot.id == routeCode
); );
if (timeSlot) { if (timeSlot) {
return { return {
date: this.dateAndTimeSlotData.date, timeSlot: {
date: this.timeSlotsForSelectedDate.date,
routeCode: timeSlot.id, routeCode: timeSlot.id,
startTime: timeSlot.startTime, startTime: timeSlot.startTime,
endTime: timeSlot.endTime, endTime: timeSlot.endTime,
jobMaxMinutes: this.estimatedServiceMinutesMaximum.toString(), jobMaxMinutes: this.estimatedServiceMinutesMaximum.toString(),
jobMinMinutes: this.estimatedServiceMinutesMinimum.toString(), jobMinMinutes: this.estimatedServiceMinutesMinimum.toString(),
},
isPremiumAppointment: routeCodeIncludesPremium ? true : false,
}; };
} }
return { return {
timeSlot: {
date: null, date: null,
startTime: null, startTime: null,
endTime: null, endTime: null,
routeCode: null, routeCode: null,
jobMaxMinutes: null, jobMaxMinutes: null,
jobMinMinutes: null, jobMinMinutes: null,
},
isPremiumAppointment: null,
}; };
}, },
}, },
@ -473,12 +501,10 @@ export default {
}, },
selectedDate: { selectedDate: {
handler() { handler() {
this.selectedTimeSlotId = null; this.selectedRouteCode = null;
this.autoSelectTimeSlotIfOnlyOneIsAvailable();
}, },
}, },
availableTimeSlots(newValue) {
this.autoSelectTimeSlotIfOnlyOneIsAvailable(newValue);
},
}, },
components: { components: {
modal, modal,

View file

@ -5,7 +5,7 @@
<label <label
for="mobileLocationLinkPromptId" for="mobileLocationLinkPromptId"
:aria-label="mobileLocationLinkPromptText" :aria-label="mobileLocationLinkPromptText"
class="form-label fw-bold w-100 ps-4 pe-4 pt-4" class="form-label fw-bold w-100 ps-4 pe-4 pt-4 text-black"
v-html="mobileLocationLinkPromptText"></label> v-html="mobileLocationLinkPromptText"></label>
<div class="update-mobile-location-text-link"> <div class="update-mobile-location-text-link">
<textLink <textLink
@ -309,4 +309,7 @@ export default {
#mobileLocationLinkPromptId { #mobileLocationLinkPromptId {
white-space: pre-line; white-space: pre-line;
} }
.text-black {
color: $black;
}
</style> </style>

View file

@ -430,6 +430,7 @@ export default {
.question-text { .question-text {
& > span { & > span {
text-align: center; text-align: center;
color: $black;
} }
} }
</style> </style>

View file

@ -21,7 +21,7 @@
<div <div
v-if="!isLoaderDisplayed" v-if="!isLoaderDisplayed"
class="availability-badge" class="availability-badge"
:class="availabilityRating == 'high' ? 'green' : 'red'"></div> :class="availabilityRating == 'high' ? 'green' : 'orange'"></div>
<span v-if="!isLoaderDisplayed" class="m-0 button-auxillary-copy">{{ <span v-if="!isLoaderDisplayed" class="m-0 button-auxillary-copy">{{
badgeText badgeText
}}</span> }}</span>
@ -88,7 +88,7 @@ export default {
if (this.availabilityRating == null) { if (this.availabilityRating == null) {
return "gray"; return "gray";
} else { } else {
return this.availabilityRating == "high" ? "green" : "red"; return this.availabilityRating == "high" ? "green" : "orange";
} }
}, },
badgeText() { badgeText() {
@ -187,11 +187,10 @@ export default {
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 13 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.5 12C3.19159 12 0.5 9.30841 0.5 6C0.5 2.69159 3.19159 0 6.5 0C9.80841 0 12.5 2.69159 12.5 6C12.5 9.30841 9.80841 12 6.5 12ZM6.5 0.785047C3.62449 0.785047 1.28505 3.12449 1.28505 6C1.28505 8.87551 3.62449 11.215 6.5 11.215C9.37551 11.215 11.715 8.87551 11.715 6C11.715 3.12449 9.37551 0.785047 6.5 0.785047Z' fill='%23006A36'/%3E%3Cpath d='M5.697 7.95252C5.5927 7.95252 5.49289 7.91102 5.41999 7.837L3.90597 6.32299C3.75233 6.16934 3.75233 5.92149 3.90597 5.76785C4.05962 5.6142 4.30747 5.6142 4.46111 5.76785L5.69812 7.00373L8.53999 4.16186C8.69364 4.00822 8.94149 4.00822 9.09513 4.16186C9.24878 4.31551 9.24878 4.56336 9.09513 4.717L5.97626 7.83588C5.90224 7.9099 5.80242 7.9514 5.69925 7.9514L5.697 7.95252Z' fill='%23006A36'/%3E%3C/svg%3E%0A"); background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 13 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.5 12C3.19159 12 0.5 9.30841 0.5 6C0.5 2.69159 3.19159 0 6.5 0C9.80841 0 12.5 2.69159 12.5 6C12.5 9.30841 9.80841 12 6.5 12ZM6.5 0.785047C3.62449 0.785047 1.28505 3.12449 1.28505 6C1.28505 8.87551 3.62449 11.215 6.5 11.215C9.37551 11.215 11.715 8.87551 11.715 6C11.715 3.12449 9.37551 0.785047 6.5 0.785047Z' fill='%23006A36'/%3E%3Cpath d='M5.697 7.95252C5.5927 7.95252 5.49289 7.91102 5.41999 7.837L3.90597 6.32299C3.75233 6.16934 3.75233 5.92149 3.90597 5.76785C4.05962 5.6142 4.30747 5.6142 4.46111 5.76785L5.69812 7.00373L8.53999 4.16186C8.69364 4.00822 8.94149 4.00822 9.09513 4.16186C9.24878 4.31551 9.24878 4.56336 9.09513 4.717L5.97626 7.83588C5.90224 7.9099 5.80242 7.9514 5.69925 7.9514L5.697 7.95252Z' fill='%23006A36'/%3E%3C/svg%3E%0A");
} }
&.red { &.orange {
background-image: url("data:image/svg+xml,%3Csvg width='13' height='12' viewBox='0 0 13 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.5 12C3.19159 12 0.5 9.30841 0.5 6C0.5 2.69159 3.19159 0 6.5 0C9.80841 0 12.5 2.69159 12.5 6C12.5 9.30841 9.80841 12 6.5 12ZM6.5 0.785047C3.62449 0.785047 1.28505 3.12449 1.28505 6C1.28505 8.87551 3.62449 11.215 6.5 11.215C9.37551 11.215 11.715 8.87551 11.715 6C11.715 3.12449 9.37551 0.785047 6.5 0.785047Z' fill='%23006A36'/%3E%3Csvg width='13' height='12' viewBox='0 0 13 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.5 0C9.81368 0 12.5 2.68632 12.5 6C12.5 9.31368 9.81368 12 6.5 12C3.18632 12 0.5 9.31368 0.5 6C0.5 2.68632 3.18632 0 6.5 0ZM6.5 0.84C3.6548 0.84 1.34 3.1548 1.34 6C1.34 8.8452 3.6548 11.16 6.5 11.16C9.3452 11.16 11.66 8.8452 11.66 6C11.66 3.1548 9.3452 0.84 6.5 0.84ZM7.90018 4.00596C8.06422 3.84204 8.33002 3.84192 8.49406 4.00596C8.6581 4.17 8.6581 4.43592 8.49406 4.59996L7.0939 6L8.49406 7.40004C8.6581 7.56408 8.6581 7.83 8.49406 7.99404C8.4121 8.076 8.30458 8.11704 8.19706 8.11704C8.08966 8.11704 7.98214 8.076 7.90018 7.99404L6.50002 6.594L5.09986 7.99404C5.01778 8.076 4.91038 8.11704 4.80286 8.11704C4.69546 8.11704 4.58794 8.076 4.50598 7.99404C4.34182 7.83 4.34182 7.56408 4.50598 7.40004L5.90614 6L4.50598 4.59996C4.34182 4.43592 4.34182 4.17 4.50598 4.00596C4.66978 3.84192 4.93582 3.84204 5.09986 4.00596L6.50002 5.406L7.90018 4.00596Z' fill='%23AC160B'/%3E%3C/svg%3E%3C/svg%3E%0A"); background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 13 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.5 0C9.81368 0 12.5 2.68632 12.5 6C12.5 9.31368 9.81368 12 6.5 12C3.18632 12 0.5 9.31368 0.5 6C0.5 2.68632 3.18632 0 6.5 0ZM6.5 0.84C3.6548 0.84 1.34 3.1548 1.34 6C1.34 8.8452 3.6548 11.16 6.5 11.16C9.3452 11.16 11.66 8.8452 11.66 6C11.66 3.1548 9.3452 0.84 6.5 0.84ZM7.90018 4.00596C8.06422 3.84204 8.33002 3.84192 8.49406 4.00596C8.6581 4.17 8.6581 4.43592 8.49406 4.59996L7.0939 6L8.49406 7.40004C8.6581 7.56408 8.6581 7.83 8.49406 7.99404C8.4121 8.076 8.30458 8.11704 8.19706 8.11704C8.08966 8.11704 7.98214 8.076 7.90018 7.99404L6.50002 6.594L5.09986 7.99404C5.01778 8.076 4.91038 8.11704 4.80286 8.11704C4.69546 8.11704 4.58794 8.076 4.50598 7.99404C4.34182 7.83 4.34182 7.56408 4.50598 7.40004L5.90614 6L4.50598 4.59996C4.34182 4.43592 4.34182 4.17 4.50598 4.00596C4.66978 3.84192 4.93582 3.84204 5.09986 4.00596L6.50002 5.406L7.90018 4.00596Z' fill='%23E86421'/%3E%3C/svg%3E%0A");
} }
} }
.button-auxillary-copy { .button-auxillary-copy {
box-sizing: border-box; box-sizing: border-box;
justify-content: right; justify-content: right;
@ -218,9 +217,9 @@ export default {
background-color: $green-100; background-color: $green-100;
} }
&.red { &.orange {
color: $red-600; color: $orange-600;
background-color: $red-100; background-color: $orange-100;
} }
&.gray { &.gray {

View file

@ -1,5 +1,9 @@
<template> <template>
<dropdownQuestion disableAutoFill v-model="selectedValue" :valueAsKey="true" /> <dropdownQuestion
disableAutoFill
v-model="selectedValue"
:valueAsKey="true"
:disableValidationIfElementDisabled="true" />
</template> </template>
<script> <script>

View file

@ -112,17 +112,15 @@ export default {
selectedMake: this.selectedMakefromStore(), selectedMake: this.selectedMakefromStore(),
selectedModel: this.selectedModelfromStore(), selectedModel: this.selectedModelfromStore(),
selectedStyle: this.selectedStylefromStore(), selectedStyle: this.selectedStylefromStore(),
carId: null, carId: this.getCarIdfromStore(),
category: null, category: this.getCategoryfromStore(),
imageUrl: null, imageUrl: this.getImagefromStore(),
imageVifNumber: null, imageVifNumber: this.getImageVifNumberfromStore(),
imageVifColor: null, imageVifColor: this.getImageVifColorfromStore(),
yearOptions: [], yearOptions: [],
makeOptions: [], makeOptions: [],
modelOptions: [], modelOptions: [],
styleOptions: [], styleOptions: [],
displayGeneric: this.displayGenericFromStore(),
unmatchedVehicleIcon: false,
}; };
}, },
@ -242,19 +240,24 @@ export default {
} }
} else { } else {
this.makeOptions = []; this.makeOptions = [];
this.selectedMake = null;
this.selectedModel = null;
this.selectedStyle = null;
} }
}, },
async selectedMake(make) { async selectedMake(make) {
if (make) { if (make) {
const result = await this.getModelOptions(this.selectedYear, make); const result = await this.getModelOptions(this.selectedYear, make);
this.modelOptions = result?.data; this.modelOptions = result?.data;
if (this.selectedMakefromStore() !== make) {
if (this.modelOptions.length === 1) this.selectedModel = this.modelOptions[0]; if (this.modelOptions.length === 1) this.selectedModel = this.modelOptions[0];
else this.selectedModel = null; else {
this.selectedModel = null;
this.selectedStyle = null; this.selectedStyle = null;
} }
} else { } else {
this.modelOptions = []; this.modelOptions = [];
this.selectedModel = null;
this.selectedStyle = null;
} }
}, },
async selectedModel(model) { async selectedModel(model) {
@ -265,16 +268,16 @@ export default {
model model
); );
this.styleOptions = result?.data; this.styleOptions = result?.data;
if (this.selectedModelfromStore() !== model) {
if (this.styleOptions.length === 1) this.selectedStyle = this.styleOptions[0]; if (this.styleOptions.length === 1) this.selectedStyle = this.styleOptions[0];
else this.selectedStyle = null; else this.selectedStyle = null;
}
} else { } else {
this.styleOptions = []; this.styleOptions = [];
this.selectedStyle = null;
} }
}, },
async selectedStyle(style) { async selectedStyle(style) {
if (style) { if (style) {
this.carId = null;
const result = await this.getVehicle( const result = await this.getVehicle(
this.selectedYear, this.selectedYear,
this.selectedMake, this.selectedMake,
@ -286,17 +289,23 @@ export default {
this.imageUrl = result?.data.imageUrl; this.imageUrl = result?.data.imageUrl;
this.imageVifNumber = result?.data.imageVifNumber; this.imageVifNumber = result?.data.imageVifNumber;
this.imageVifColor = result?.data.imageVifColor; this.imageVifColor = result?.data.imageVifColor;
if (this.imageUrl == null) {
this.displayGeneric = false;
this.unmatchedVehicleIcon = true;
} else this.unmatchedVehicleIcon = false;
} else { } else {
this.imageUrl = null; this.imageUrl = null;
this.displayGeneric = true;
} }
}, },
}, },
computed: {
unmatchedVehicleIcon() {
if (this.imageUrl == null && this.carId !== null) return true;
else return false;
},
displayGeneric() {
if (!this.selectedStyle) return true;
else if (this.carId == null) return true;
else if (this.imageUrl == null && this.carId !== null) return false;
else return true;
},
},
methods: { methods: {
getVehicle(year, make, model, style) { getVehicle(year, make, model, style) {
return this.dispatchStoreAction(this.storeActions.GET_VEHICLE, { return this.dispatchStoreAction(this.storeActions.GET_VEHICLE, {
@ -306,15 +315,6 @@ export default {
style: style, style: style,
}); });
}, },
displayGenericFromStore() {
if (store.getters.vehicle.imageUrl !== null) return false;
else if (
store.getters.vehicle.imageUrl === null &&
store.getters.vehicle.carId !== null
)
return false;
else return true;
},
arePagePrerequisitesValid() { arePagePrerequisitesValid() {
return true; return true;
}, },
@ -377,6 +377,21 @@ export default {
model: model, model: model,
}); });
}, },
getImagefromStore() {
return store.getters.vehicle.imageUrl;
},
getCarIdfromStore() {
return store.getters.vehicle.carId;
},
getCategoryfromStore() {
return store.getters.vehicle.category;
},
getImageVifNumberfromStore() {
return store.getters.vehicle.imageVifNumber;
},
getImageVifColorfromStore() {
return store.getters.vehicle.imageVifColor;
},
}, },
components: { components: {

View file

@ -26,6 +26,7 @@ const navigationScenarios = {
SELECTED_LICENSE_PLATE: "SELECTED_LICENSE_PLATE", SELECTED_LICENSE_PLATE: "SELECTED_LICENSE_PLATE",
SELECTED_HOME_ADDRESS: "SELECTED_HOME_ADDRESS", SELECTED_HOME_ADDRESS: "SELECTED_HOME_ADDRESS",
CLICKED_FORWARD_WITH_NO_QUESTIONS: "CLICKED_FORWARD_WITH_NO_QUESTIONS", CLICKED_FORWARD_WITH_NO_QUESTIONS: "CLICKED_FORWARD_WITH_NO_QUESTIONS",
CLICKED_VIN_RETRY: "CLICKED_VIN_RETRY",
// Part selection // Part selection
CLICKED_FORWARD_WITH_PART_QUESTIONS: "CLICKED_FORWARD_WITH_PART_QUESTIONS", CLICKED_FORWARD_WITH_PART_QUESTIONS: "CLICKED_FORWARD_WITH_PART_QUESTIONS",
@ -40,12 +41,16 @@ const navigationScenarios = {
CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS: "CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS", CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS: "CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS",
CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS: "CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS", CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS: "CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS",
// Schedule
CLICKED_CHANGE_LOCATION: "CLICKED_CHANGE_LOCATION",
// Review // Review
CLICKED_VEHICLE_EDIT: "CLICKED_VEHICLE_EDIT", CLICKED_VEHICLE_EDIT: "CLICKED_VEHICLE_EDIT",
CLICKED_DAMAGE_EDIT: "CLICKED_DAMAGE_EDIT", CLICKED_DAMAGE_EDIT: "CLICKED_DAMAGE_EDIT",
CLICKED_SERVICE_PACKAGE_EDIT: "CLICKED_SERVICE_PACKAGE_EDIT", CLICKED_SERVICE_PACKAGE_EDIT: "CLICKED_SERVICE_PACKAGE_EDIT",
CLICKED_SERVICE_LOCATION_EDIT: "CLICKED_SERVICE_LOCATION_EDIT", CLICKED_SERVICE_LOCATION_EDIT: "CLICKED_SERVICE_LOCATION_EDIT",
CLICKED_SCHEDULE_EDIT: "CLICKED_SCHEDULE_EDIT", CLICKED_SCHEDULE_EDIT: "CLICKED_SCHEDULE_EDIT",
CLICKED_CUSTOMER_EDIT: "CLICKED_CUSTOMER_EDIT",
}; };
export { navigationScenarios }; export { navigationScenarios };

View file

@ -76,6 +76,10 @@ const routingTable = function (store) {
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE, destinationFmgPageValue: fmgPageValues.QUOTE,
}, },
{
scenario: navigationScenarios.CLICKED_VIN_RETRY,
destinationFmgPageValue: fmgPageValues.ESTIMATE,
},
], ],
}, },
{ {
@ -109,6 +113,10 @@ const routingTable = function (store) {
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE, destinationFmgPageValue: fmgPageValues.QUOTE,
}, },
{
scenario: navigationScenarios.CLICKED_VIN_RETRY,
destinationFmgPageValue: fmgPageValues.ESTIMATE,
},
], ],
}, },
{ {
@ -146,6 +154,10 @@ const routingTable = function (store) {
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE, destinationFmgPageValue: fmgPageValues.QUOTE,
}, },
{
scenario: navigationScenarios.CLICKED_VIN_RETRY,
destinationFmgPageValue: fmgPageValues.ESTIMATE,
},
], ],
}, },
{ {
@ -179,6 +191,10 @@ const routingTable = function (store) {
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE, destinationFmgPageValue: fmgPageValues.QUOTE,
}, },
{
scenario: navigationScenarios.CLICKED_VIN_RETRY,
destinationFmgPageValue: fmgPageValues.ESTIMATE,
},
], ],
}, },
{ {
@ -399,6 +415,10 @@ const routingTable = function (store) {
scenario: navigationScenarios.CLICKED_FORWARD, scenario: navigationScenarios.CLICKED_FORWARD,
destinationFmgPageValue: fmgPageValues.CUSTOMER_DETAILS, destinationFmgPageValue: fmgPageValues.CUSTOMER_DETAILS,
}, },
{
scenario: navigationScenarios.CLICKED_CHANGE_LOCATION,
destinationFmgPageValue: fmgPageValues.SERVICE_LOCATION,
},
], ],
}, },
{ {
@ -441,6 +461,10 @@ const routingTable = function (store) {
scenario: navigationScenarios.CLICKED_SCHEDULE_EDIT, scenario: navigationScenarios.CLICKED_SCHEDULE_EDIT,
destinationFmgPageValue: fmgPageValues.SCHEDULE, destinationFmgPageValue: fmgPageValues.SCHEDULE,
}, },
{
scenario: navigationScenarios.CLICKED_CUSTOMER_EDIT,
destinationFmgPageValue: fmgPageValues.CUSTOMER_DETAILS,
},
], ],
}, },
]; ];

View file

@ -336,8 +336,6 @@ export const mutations = {
}, },
resetRegistrationState(state) { resetRegistrationState(state) {
state.order.vehicle.registration.licensePlate = null; state.order.vehicle.registration.licensePlate = null;
state.order.customer.firstName = null;
state.order.customer.lastName = null;
}, },
resetGlassPartsState(state) { resetGlassPartsState(state) {
state.order.lineItems.glassParts = null; state.order.lineItems.glassParts = null;
@ -481,7 +479,7 @@ export const mutations = {
state.order.customer.emailAddress = sessionInformation.order.customer.emailAddress; state.order.customer.emailAddress = sessionInformation.order.customer.emailAddress;
state.order.customer.firstName = sessionInformation.order.customer.firstName; state.order.customer.firstName = sessionInformation.order.customer.firstName;
state.order.customer.lastName = sessionInformation.order.customer.lastName; state.order.customer.lastName = sessionInformation.order.customer.lastName;
state.order.customer.phoneNumber = sessionInformation.order.customer.servicePhoneNumber; state.order.customer.phoneNumber = sessionInformation.order.customer.phoneNumber;
state.order.customer.isSmsOptIn = sessionInformation.order.customer.isSmsOptIn; state.order.customer.isSmsOptIn = sessionInformation.order.customer.isSmsOptIn;
state.order.existingPromoCode = sessionInformation.order.existingPromoCode; state.order.existingPromoCode = sessionInformation.order.existingPromoCode;
@ -1436,7 +1434,7 @@ export const actions = {
firstName: order.customer.firstName, firstName: order.customer.firstName,
lastName: order.customer.lastName, lastName: order.customer.lastName,
isSmsOptIn: order.customer.isSmsOptIn, isSmsOptIn: order.customer.isSmsOptIn,
servicePhoneNumber: order.customer.phoneNumber, phoneNumber: order.customer.phoneNumber,
}, },
damage: { damage: {
numberOfChips: damage.numberOfChips, numberOfChips: damage.numberOfChips,
@ -1675,10 +1673,7 @@ export const actions = {
{ isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo } { isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }
) { ) {
//Reset dependent state when changing //Reset dependent state when changing
if ( if (vehicleInfo.vin !== context.state.order.vehicle.vin) {
registrationInfo?.firstName !== context.state.order.customer?.firstName ||
registrationInfo?.lastName !== context.state.order.customer?.lastName
) {
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES); context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
if (!isSelectedGlassAvailableForVehicle) { if (!isSelectedGlassAvailableForVehicle) {

View file

@ -60,6 +60,10 @@ $gray-700: #303333; // Used in theme
$gray-800: #222424; // Used in theme $gray-800: #222424; // Used in theme
$gray-900: #181a1a; // Used in theme $gray-900: #181a1a; // Used in theme
//orange
$orange-600: #e86421;
$orange-100: #fff5eb;
// Miscellaneous Colors // Miscellaneous Colors
$indigo: #6610f2; $indigo: #6610f2;
$purple: #6f42c1; $purple: #6f42c1;

View file

@ -1,4 +1,5 @@
<template> <template>
<loadingModal v-if="useLoadingModal" notFullScreen ref="loadingModal" />
<a v-if="linkType === 'navigation'" @click="handleClick" class="navigation-link" :href="href" <a v-if="linkType === 'navigation'" @click="handleClick" class="navigation-link" :href="href"
>{{ text }}<slot name="after-text"></slot >{{ text }}<slot name="after-text"></slot
></a> ></a>
@ -19,6 +20,8 @@
</template> </template>
<script> <script>
import loadingModal from "@/fmg-components/loading-modal/loading-modal";
export default { export default {
name: "textLink", name: "textLink",
props: { props: {
@ -27,6 +30,10 @@ export default {
href: { href: {
type: String, type: String,
}, },
useLoadingModal: {
type: Boolean,
default: false,
},
}, },
methods: { methods: {
handleClick(event) { handleClick(event) {
@ -36,9 +43,17 @@ export default {
this.text, this.text,
true true
); );
if (this.useLoadingModal) {
this.$refs.loadingModal.showModal();
}
this.$emit("click-event"); this.$emit("click-event");
}, },
}, },
components: {
loadingModal,
},
}; };
</script> </script>