diff --git a/src/layouts/quote/cash-or-insurance-question/cash-or-insurance-question.vue b/src/layouts/quote/cash-or-insurance-question/cash-or-insurance-question.vue index 45fef683a..f5fec2255 100644 --- a/src/layouts/quote/cash-or-insurance-question/cash-or-insurance-question.vue +++ b/src/layouts/quote/cash-or-insurance-question/cash-or-insurance-question.vue @@ -5,7 +5,7 @@ :groupName="groupName" buttonTypeString="listButtonHorizontal" v-model="selectedValues" - :additionalButtonData="{ additionalButtonStyling: 'listButtonHorizontalStrong' }" + :additionalButtonData="additionalButtonData" isRequired /> @@ -27,6 +27,11 @@ export default { answersFromCms() { return this.getCmsContent(this.cmsWidgetName, "Answers"); }, + additionalButtonData() { + return { + additionalButtonStyling: 'listButtonHorizontalStrong' + }; + }, selectedValues: { get: function () { // Convert to CMS answer name from bool diff --git a/src/layouts/schedule/constants/schedule-constants.js b/src/layouts/schedule/constants/schedule-constants.js new file mode 100644 index 000000000..aae307675 --- /dev/null +++ b/src/layouts/schedule/constants/schedule-constants.js @@ -0,0 +1,11 @@ +const AppointmentTypeStrings = { + IN_SHOP: "Inshop", + MOBILE: "Mobile", + DROP_OFF: "Dropoff", +}; + +const PREMIUM_TIME_SLOT_ID_FLAG = "-premium"; + +const PREMIUM_FEE_PART_TYPE = "EARLY BIRD"; + +export { AppointmentTypeStrings, PREMIUM_TIME_SLOT_ID_FLAG, PREMIUM_FEE_PART_TYPE }; \ No newline at end of file diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 60fb90b75..6c859c68b 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -39,13 +39,14 @@ @@ -49,15 +49,13 @@ import { defineRule, useField } from "vee-validate"; import { errorMessages } from "@/constants/error-messages"; import { required } from "@/helpers/validation-rules"; +// Constants +import { AppointmentTypeStrings, PREMIUM_TIME_SLOT_ID_FLAG, PREMIUM_FEE_PART_TYPE} from "../constants/schedule-constants"; + // Validation for the modal button defineRule("time-slot-required", required(errorMessages.OPTION_REQUIRED)); // Constants -const AppointmentTypeStrings = { - IN_SHOP: "Inshop", - MOBILE: "Mobile", - DROP_OFF: "Dropoff", -}; export default { name: "timeSlotModalQuestion", @@ -65,18 +63,20 @@ export default { modelValue: Object, cmsWidgetName: String, mobileCmsWidgetName: String, - earlyBirdCmsWidgetName: String, + mobilePremiumCmsWidgetName: String, dropoffCmsWidgetName: String, + sameDayDropOffCmsWidgetName: String, appointmentType: String, dateAndTimeSlotData: Object, - mobileEarlyBirdFee: Object, + premiumAppointmentFee: Object, estimatedServiceMinutesMinimum: Number, estimatedServiceMinutesMaximum: Number, validationRules: String, }, data() { return { - selectedTimeSlot: null, + selectedTimeSlotId: null, + isSelectedAppointmentPremium: null, timeslotModalListButton: timeslotModalListButton, }; }, @@ -89,42 +89,36 @@ export default { }, watch: { modelValue() { - this.selectedTimeSlot = this.modelValue; // Run component validation that is used at parent level - this.handleChange(this.modelValue); + this.handleChange(this.modelValue.id); }, - // dateAndTimeSlotData(newValue, oldValue) { - // const numberOfOptions = newValue?.timeSlots.length; - // console.log('running'); - // if (numberOfOptions === 1) { - // this.selectedTimeSlot = newValue.timeSlots[0].id; - // } - // } + dateAndTimeSlotData(newValue, oldValue) { + const numberOfOptions = newValue?.timeSlots.length; + if (numberOfOptions === 1) { + this.selectedTimeSlotId = newValue.timeSlots[0].id; + } + } }, computed: { supplementalInformationBlock() { let appointmentTypeCmsWidgetName; - let cmsFieldName = "BodyText"; if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) { return null; } else if (this.appointmentType === AppointmentTypeStrings.MOBILE) { appointmentTypeCmsWidgetName = - this.selectedTimeSlot === this.earlyBirdButtonText - ? this.earlyBirdCmsWidgetName + this.selectedTimeSlotId === this.premiumAppointmentButtonText + ? this.mobilePremiumCmsWidgetName : this.mobileCmsWidgetName; } else { - appointmentTypeCmsWidgetName = this.dropoffCmsWidgetName; - if (this.isSameDay) { - cmsFieldName = "BodyText2"; - } + appointmentTypeCmsWidgetName = this.isSameDay ? this.sameDayDropOffCmsWidgetName : this.dropoffCmsWidgetName; } - return this.getCmsContent(appointmentTypeCmsWidgetName, cmsFieldName); + return this.getCmsContent(appointmentTypeCmsWidgetName, "BodyText"); }, footerCloseButtonText() { return this.getCmsContent(this.cmsWidgetName, "FooterText"); }, - earlyBirdButtonText() { - return this.getCmsContent(this.earlyBirdCmsWidgetName, "HeaderText"); + premiumAppointmentButtonText() { + return this.getCmsContent(this.mobilePremiumCmsWidgetName, "HeaderText"); }, dropoffButtonText() { return this.getCmsContent(this.dropoffCmsWidgetName, "HeaderText"); @@ -136,14 +130,11 @@ export default { return this.getCmsContent(this.dropoffCmsWidgetName, "SubheaderText"); }, inshopDurationText() { - const inshopDurationTextWithoutTime = this.getCmsContent( - this.cmsWidgetName, - "SubheaderText" - ); + const inshopDurationTextWithoutTime = this.getCmsContent(this.cmsWidgetName, "SubheaderText"); const inshopDurationTime = this.getDisplayTextForDurationLength( - this.estimatedServiceMinutesMinimum, - this.estimatedServiceMinutesMaximum - ); + this.estimatedServiceMinutesMinimum, + this.estimatedServiceMinutesMaximum + ); return `${inshopDurationTextWithoutTime} ${inshopDurationTime}`; }, durationTextBlockCopy() { @@ -203,13 +194,18 @@ export default { } if (this.appointmentType === AppointmentTypeStrings.MOBILE) { - const offerPremium = this.dateAndTimeSlotData.timeSlots[0].offerPremium; - const hasEarlyBird = this.mobileEarlyBirdFee?.partType === "EARLY BIRD"; - if (offerPremium && hasEarlyBird) { + const isPremiumTimeSlot = this.dateAndTimeSlotData.timeSlots[0].offerPremium; + const hasPremiumPartAvailable = this.premiumAppointmentFee?.partType === PREMIUM_FEE_PART_TYPE; + if (isPremiumTimeSlot && hasPremiumPartAvailable) { + const formattedPrice = "+$" + this.getTotalLineItemPrice(this.premiumAppointmentFee).toFixed(2); availableTimeSlots.unshift({ - value: this.dateAndTimeSlotData.timeSlots[0].id + "-earlybird", - buttonLabel: this.earlyBirdButtonText, - buttonLabelSubCopy: this.getTotalLineItemPrice(this.mobileEarlyBirdFee), + // Unique value is required for each and the premium appoinment shares a timeslot ID + value: this.addPremiumFlagToInput(this.dateAndTimeSlotData.timeSlots[0].id), + buttonLabel: this.premiumAppointmentButtonText, + buttonLabelSubCopy: formattedPrice, + additionalButtonData: { + isPremiumAppointment: true + } }); } } @@ -222,12 +218,27 @@ export default { }, // fires any time the footer button is used, is fired before "onModalClosed" closeModal() { - this.$emit("update:modelValue", this.selectedTimeSlot); + if (this.selectedTimeSlotId.toString().includes(PREMIUM_TIME_SLOT_ID_FLAG)) { + this.selectedTimeSlotId = this.removePremiumFlagFromInput(this.selectedTimeSlotId); + this.isSelectedAppointmentPremium = true; + } else { + this.isSelectedAppointmentPremium = false; + } + const selectedTimeSlotData = { + id: this.selectedTimeSlotId, + isPremiumAppointment: this.isSelectedAppointmentPremium, + } + this.$emit("update:modelValue", selectedTimeSlotData); this.$refs["timeSlots"].closeModal(); }, // fires any time the modal is closed, AFTER "closeModal" fires if footer button is used onModalClosed() { - this.selectedTimeSlot = this.modelValue; + this.isSelectedAppointmentPremium = this.modelValue.isPremiumAppointment; + if (this.isSelectedAppointmentPremium) { + this.selectedTimeSlotId = this.addPremiumFlagToInput(this.modelValue.id); + } else { + this.selectedTimeSlotId = this.modelValue.id; + } this.$emit("time-slot-modal-closed"); }, // Expected input: "HH:MM:SS" @@ -252,6 +263,12 @@ export default { } return displayTextForDurationLength; }, + addPremiumFlagToInput(timeSlotId) { + return timeSlotId += PREMIUM_TIME_SLOT_ID_FLAG; + }, + removePremiumFlagFromInput(timeSlotId) { + return parseInt(timeSlotId.trim(PREMIUM_TIME_SLOT_ID_FLAG.length)); + }, }, components: { modal, diff --git a/src/layouts/schedule/time-slot-modal-question/timeslot-modal-list-button/timeslot-modal-list-button.vue b/src/layouts/schedule/time-slot-modal-question/timeslot-modal-list-button/timeslot-modal-list-button.vue index 60b2f9e81..8c71a9bb3 100644 --- a/src/layouts/schedule/time-slot-modal-question/timeslot-modal-list-button/timeslot-modal-list-button.vue +++ b/src/layouts/schedule/time-slot-modal-question/timeslot-modal-list-button/timeslot-modal-list-button.vue @@ -6,12 +6,13 @@
- + {{ buttonLabel }} - - + {{ formattedButtonLabelSubCopy }} + + {{ screenReaderOnlyText }} @@ -28,7 +29,7 @@ import baseInputButton from "@/digital-components/base-input-button/base-input-b import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin"; export default { - name: "timeslotMOdalListButton", + name: "timeslotModalListButton", mixins: [inputButtonWrapperMixin], props: { loaderColor: String, @@ -44,7 +45,7 @@ export default { }, computed: { formattedButtonLabelSubCopy() { - return this.buttonLabelSubCopy?.toFixed(2); + return this.buttonLabelSubCopy; }, }, methods: { @@ -85,6 +86,9 @@ export default { font-weight: 500; background: $blue-100; box-shadow: 0 0 0 1px $blue; + span.premium-appointment-price { + background: $green-200; + } } &:checked:focus + .list-button-content { box-shadow: 0 0 0 2.5px $blue; @@ -109,11 +113,22 @@ export default { width: 100%; outline: none; - span { - &.small { - font-size: 0.75rem; - color: $gray-550; - } + span.premium-appointment-price { + position: absolute; + background: $green-100; + border-radius: 4.5rem; + line-height: 1.25rem; + color: $green-700; + font-size: 0.75rem; + margin-left:4px; + padding: 2px 8px; + font-weight: 500; } } + +.position-relative { + position: relative; +} + +