CASH-2378: new component for multi location modal

This commit is contained in:
Adam Caouette 2026-03-04 08:01:13 -05:00
parent 0c32eecdcc
commit b3fb548813

View file

@ -0,0 +1,317 @@
<template>
<div id="multi-location-modal-container">
<modal
:ref="modalName"
:headerText="modalHeaderText"
suppressPageScroll
:onModalClosedCallback="onModalClosed"
:footerButtonText="modalFooterText"
:isFooterButtonPrimary="true"
@footer-button-event="confirmAppointment"
@isModalOpened="setIsModalOpen">
<buttonQuestion
ref="buttonQuestion"
buttonTypeString="multiLocationRadioButton"
:buttonTypeObject="multiLocationRadioButton"
class="mt-5"
:answers="availableAppointments"
groupName="multiLocationQuestion"
textPosition="text-center"
v-model="selectedValue"
isRequired
validationRules="time-slot-required" />
<template v-slot:modal-header-slot>
<span>{{ modalSubHeaderText }}</span>
</template>
<template v-slot:modal-footer-slot>
<button
type="button"
class="btn btn-link"
id="see-more-options"
:disabled="isLoading"
@click="closeModal">
{{ buttonText }}
</button>
</template>
</modal>
</div>
</template>
<script>
// Supporting files
import store from "@/store";
import modal from "@/digital-components/modal/modal";
import { splitCopyOnCMSPlaceHolder } from "@/helpers/cms-content-helper";
import { get12HourTimeFormat } from "@/helpers/date-helper";
import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helper";
import buttonQuestion from "@/digital-components/button-question/button-question";
import multiLocationRadioButton from "./multi-location-radio/multi-location-radio";
// Validation
import { defineRule, useField } from "vee-validate";
import { errorMessages } from "@/constants/error-messages";
import { required } from "@/helpers/validation-rules";
const NULL_APPOINTMENT = {
estimatedServiceMinutes: {
minimum: null,
maximum: null,
},
timeSlot: null,
date: null,
};
// Validation for the modal button
defineRule("time-slot-required", required(errorMessages.OPTION_REQUIRED));
export default {
name: "multi-location-modal",
props: {
cmsWidgetName: String,
modalWidgetName: String,
buttonWidgetName: String,
},
data() {
return {
isModalOpen: false,
mobileAppointmentOption: NULL_APPOINTMENT,
inshopAppointmentOption: NULL_APPOINTMENT,
confirmedAppointment: false,
multiLocationRadioButton: multiLocationRadioButton,
selectedValue: null,
};
},
methods: {
splitCopyOnCMSPlaceHolder,
get12HourTimeFormat,
openModal() {
this.modal.openModal();
},
onModalClosed() {
this.pushEvent();
this.$emit("close-mobile-first-modal");
this.confirmedAppointment = false;
},
closeModal() {
this.modal.closeModal();
},
confirmAppointment() {
if (!this.selectedAppointment) return;
const selectedTimeSlot = {
timeSlot: this.selectedAppointment.timeSlot,
routeCode: this.selectedAppointment.timeSlot.id,
date: this.selectedAppointment.date,
};
this.confirmedAppointment = true;
this.$emit("confirm-appointment", {
selectedTimeSlot: selectedTimeSlot,
appointmentType: this.selectedValue,
});
this.modal.closeModal();
},
setPromotedMobileAppointment(appointment) {
this.mobileAppointmentOption = appointment;
},
setPromotedInshopAppointment(appointment) {
this.inshopAppointmentOption = appointment;
},
getIsModalOpen() {
return this.isModalOpen;
},
setIsModalOpen(isOpen) {
this.isModalOpen = isOpen;
},
pushEvent() {
let gaAction = this.confirmedAppointment ? this.GaActions.YES : this.GaActions.NO;
if (this.mobileAppointmentOption && this.mobileAppointmentOption.timeSlot) {
this.pushEventToGA(
this.GaCategories.CONFIRMATION_CLICKED,
gaAction,
this.mobileAppointmentOption.timeSlot.startTime +
"-" +
this.mobileAppointmentOption.timeSlot.endTime +
" " +
this.mobileAppointmentOption.date,
true
);
}
},
},
computed: {
selectedAppointment() {
if (this.selectedValue?.toLowerCase() === "mobile") {
return this.mobileAppointmentOption;
} else if (this.selectedValue?.toLowerCase() === "inshop") {
return this.inshopAppointmentOption;
} else {
return NULL_APPOINTMENT;
}
},
modalName() {
return this.modalWidgetName;
},
modal() {
return this.$refs[this.modalName];
},
modalSubHeaderText() {
return this.getCmsContent(this.modalWidgetName, "SubheaderText");
},
modalHeaderText() {
return this.getCmsContent(this.modalWidgetName, "HeaderText");
},
modalFooterText() {
return this.getCmsContent(this.modalWidgetName, "FooterText");
},
buttonText() {
return this.getCmsContent(this.modalWidgetName, "FooterText2");
},
mobileButtonLabel() {
return this.getCmsContent(this.buttonWidgetName, "HeaderText");
},
inshopButtonLabel() {
return this.getCmsContent(this.buttonWidgetName, "SubheaderText");
},
mobileButtonDate() {
if (!this.mobileAppointmentOption.date) return "";
return new Date(this.mobileAppointmentOption.date).toLocaleDateString("en-US", {
timeZone: "UTC",
weekday: "short",
month: "long",
day: "numeric",
});
},
inshopButtonDate() {
if (!this.inshopAppointmentOption.date) return "";
return new Date(this.inshopAppointmentOption.date).toLocaleDateString("en-US", {
timeZone: "UTC",
weekday: "short",
month: "long",
day: "numeric",
});
},
mobileButtonTime() {
const timeslot =
this.get12HourTimeFormat(this.mobileAppointmentOption.timeSlot?.startTime) +
" - " +
this.get12HourTimeFormat(this.mobileAppointmentOption.timeSlot?.endTime);
return timeslot;
},
inshopButtonTime() {
const timeslot = this.get12HourTimeFormat(
this.inshopAppointmentOption.timeSlot?.startTime
);
return timeslot;
},
mobileLocationLabel() {
return this.getCmsContent(this.buttonWidgetName, "BodyText");
},
inshopLocationLabel() {
return this.inshopAppointmentOption?.addressCopy;
},
mobileServiceDuration() {
const durationTime = getDisplayTextForDurationLength(
this.mobileAppointmentOption.estimatedServiceMinutes.minimum,
this.mobileAppointmentOption.estimatedServiceMinutes.maximum
);
const footerText = this.getCmsContent(this.buttonWidgetName, "FooterText").replaceAll(
"{custom:serviceLength}",
durationTime
);
return footerText;
},
inshopServiceDuration() {
const durationTime = getDisplayTextForDurationLength(
this.inshopAppointmentOption.estimatedServiceMinutes.minimum,
this.inshopAppointmentOption.estimatedServiceMinutes.maximum
);
const footerText = this.getCmsContent(this.buttonWidgetName, "FooterText").replaceAll(
"{custom:serviceLength}",
durationTime
);
return footerText;
},
availableAppointments() {
return [
{
value: "mobile",
buttonLabel: this.mobileButtonDate,
buttonLabelSubCopy: this.mobileButtonTime,
buttonBodyCopy: this.mobileLocationLabel,
buttonAuxillaryCopy: this.mobileButtonLabel,
buttonFooterCopy: this.mobileServiceDuration,
},
{
value: "inshop",
buttonLabel: this.inshopButtonDate,
buttonLabelSubCopy: this.inshopButtonTime,
buttonBodyCopy: this.inshopLocationLabel,
buttonAuxillaryCopy: this.inshopButtonLabel,
buttonFooterCopy: this.inshopServiceDuration,
},
];
},
},
components: { modal, buttonQuestion },
};
</script>
<style lang="scss">
#multi-location-modal-container {
.modal-component {
@include media-breakpoint-up(md) {
.modal-dialog {
left: 0;
align-content: center;
flex-wrap: wrap;
width: 22.063rem;
transform: translate(0, 0);
.modal-content {
border-radius: $border-radius-lg;
}
}
}
.modal-dialog {
.modal-content {
.modal-header {
flex-direction: column;
& > span {
color: $red;
font-size: $font-size-14;
font-weight: $font-weight-600;
text-transform: uppercase;
}
.modal-title {
font-size: $font-size-20;
font-weight: $font-weight-normal;
}
}
.modal-body {
padding: 0.5rem 1rem;
.modal-body-inner {
ul {
list-style: none;
padding: 1rem;
background-color: #f4f4f4;
font-size: $font-size-14;
}
}
}
.modal-footer {
flex-flow: wrap-reverse;
#see-more-options {
width: 100%;
text-decoration: none;
margin-top: 1.5rem;
font-weight: $font-weight-600;
}
}
}
}
}
}
</style>