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..a2afb1a7c 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..89b4a4c1a --- /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 }; diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index d5a1283c9..654f35a15 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -28,13 +28,14 @@ @@ -49,15 +49,17 @@ 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 +67,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 +93,38 @@ 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; + if (numberOfOptions === 1) { + this.selectedTimeSlotId = newValue.timeSlots[0].id; + } }, - // dateAndTimeSlotData(newValue, oldValue) { - // const numberOfOptions = newValue?.timeSlots.length; - // console.log('running'); - // if (numberOfOptions === 1) { - // this.selectedTimeSlot = 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"); @@ -203,13 +203,20 @@ 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 +229,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 +274,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..4f5b17461 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,16 @@
- + {{ buttonLabel }} + + {{ formattedButtonLabelSubCopy }} + - - {{ formattedButtonLabelSubCopy }} - + {{ screenReaderOnlyText }} @@ -28,7 +32,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 +48,7 @@ export default { }, computed: { formattedButtonLabelSubCopy() { - return this.buttonLabelSubCopy?.toFixed(2); + return this.buttonLabelSubCopy; }, }, methods: { @@ -85,6 +89,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 +116,20 @@ 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; +}