diff --git a/src/assets/img/icons/alert-circle-yellow.svg b/src/assets/img/icons/alert-circle-yellow.svg new file mode 100644 index 000000000..904aefb98 --- /dev/null +++ b/src/assets/img/icons/alert-circle-yellow.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/constants/experiments.js b/src/constants/experiments.js index c76b1ea1c..f8691fc46 100644 --- a/src/constants/experiments.js +++ b/src/constants/experiments.js @@ -5,6 +5,7 @@ const experimentUniverses = { IGQ_SkipQuote: "NextGen_IGQSkipToInsurance", AFTERPAY_BREAKOUT_DISPLAY: "AfterpayBreakoutDisplay", MOBILE_FIRST_APPOINTMENT: "MobileFirstAppointment", + MULTI_LOCATION_POPUP: "MultiLocationPopup", }; const experimentSettings = { @@ -40,6 +41,10 @@ const experimentSettings = { SHOW_MOBILE_FIRST_POPUP_REPLACE_WITHOUT_MSR: "Show_Mobile_First_Popup_Replace_Without_MSR", SHOW_MOBILE_FIRST_POPUP_REPLACE_WITH_MSR: "Show_Mobile_First_Popup_Replace_With_MSR", OFFER_OEM: "OfferOem", + SHOW_MULTI_LOCATION_APPT: "ShowMultiLocationAppt", + SHOW_NO_AVAILABILE_DAYS_MULTI_LOCATION: "Show_NoAvailableDaysMultiLocation", + SHOW_NO_PM_DAYS_MULTI_LOCATION: "Show_NoPMDaysMultiLocation", + SHOW_PM_DAYS_MULTI_LOCATION: "Show_PMDaysMultiLocation", USE_ADYEN_PAYMENT: "UseAdyenPayment", SHOW_UNVERIFIED_DEDUCT_ENTRY: "ShowUnverifiedDeducEntry", SERVICE_PACKAGE_ORDER: "ServicePackageOrder", diff --git a/src/digital-components/button-question/button-question.vue b/src/digital-components/button-question/button-question.vue index 9862633ad..417c88809 100644 --- a/src/digital-components/button-question/button-question.vue +++ b/src/digital-components/button-question/button-question.vue @@ -226,6 +226,7 @@ export default { let classes; switch (this.buttonTypeString) { case "timeSlotModalListButton": + case "multiLocationRadioButton": case "listButton": classes = "w-100"; break; @@ -265,6 +266,8 @@ export default { classes += " radio-button-container"; } else if (this.buttonTypeString == "servicePackageRadio") { classes = "package-wrapper col"; + } else if (this.buttonTypeString == "multiLocationRadioButton") { + classes = "multi-location-wrapper col"; } if (this.buttonTypeString == "listCard" && this.buttonsInfo.length > 2) { diff --git a/src/helpers/cms-content-helper.js b/src/helpers/cms-content-helper.js index 727add296..450875d0f 100644 --- a/src/helpers/cms-content-helper.js +++ b/src/helpers/cms-content-helper.js @@ -343,3 +343,14 @@ export function splitCMSCopyOnParagraphTag(copy) { export function splitCMSCopyOnBR(copy) { return copy.split("
"); } + +// This function takes in a number and returns it formatted as USD currency, with or without cents depending on if the number is an integer or not. +export function formatToUSDollar(amount) { + const isInteger = amount % 1 === 0; + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + minimumFractionDigits: isInteger ? 0 : 2, + maximumFractionDigits: 2, + }).format(amount); +} diff --git a/src/layouts/schedule/multi-location-modal/multi-location-modal.vue b/src/layouts/schedule/multi-location-modal/multi-location-modal.vue new file mode 100644 index 000000000..3ca0693a8 --- /dev/null +++ b/src/layouts/schedule/multi-location-modal/multi-location-modal.vue @@ -0,0 +1,316 @@ + + + + diff --git a/src/layouts/schedule/multi-location-modal/multi-location-radio/multi-location-radio.spec.js b/src/layouts/schedule/multi-location-modal/multi-location-radio/multi-location-radio.spec.js new file mode 100644 index 000000000..f305d4e2e --- /dev/null +++ b/src/layouts/schedule/multi-location-modal/multi-location-radio/multi-location-radio.spec.js @@ -0,0 +1,121 @@ +import { mount } from "@vue/test-utils"; +import multiLocationRadio from "./multi-location-radio"; +import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin"; + +describe("multi-location-radio.vue", () => { + it("Should include buttonLabel in html", async () => { + // Arrange + let { wrapper } = setupMocks({ + mountOptionsMockData: { + propsData: mockProps, + }, + }); + + // Act + const outputHtml = wrapper.html(); + + // Assert + expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabel"])); + }); + + it("Should include buttonLabelSubCopy in html", async () => { + // Arrange + let { wrapper } = setupMocks({ + mountOptionsMockData: { + propsData: mockProps, + }, + }); + + // Act + const outputHtml = wrapper.html(); + + // Assert + expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabelSubCopy"])); + }); + it("Should include buttonFooterCopy in html", async () => { + // Arrange + let { wrapper } = setupMocks({ + mountOptionsMockData: { + propsData: mockProps, + }, + }); + + // Act + const outputHtml = wrapper.html(); + + // Assert + expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonFooterCopy"])); + }); + it("Should get 5 strings from getArrayOfListItemsFromRawCmsCopy when provided with a buttonBodyCopy", async () => { + // Arrange + let { wrapper } = setupMocks({ + mountOptionsMockData: { + propsData: mockProps, + }, + }); + // Act + const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy); + + // Assert + expect(results.length).toBe(5); + }); + it("Should get strings from getArrayOfListItemsFromRawCmsCopy without