Merge pull request #2865 from Safelite/rlsmerge/2025.09.25-to-dev

Rlsmerge/2025.09.25 to dev
This commit is contained in:
CarlNation 2025-09-25 14:52:25 -04:00 committed by GitHub
commit c469be54f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 572 additions and 471 deletions

View file

@ -110,18 +110,6 @@ stages:
clearFolder: true clearFolder: true
deployFolder: "" deployFolder: ""
region: us-east-2 region: us-east-2
appDeployVariables:
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
__VUE_APP_HERITAGE_FUNNEL__: $(__VUE_APP_HERITAGE_FUNNEL__)
__VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__)
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
__VUE_APP_MY_ACCOUNT__: $(__VUE_APP_MY_ACCOUNT__)
__VUE_APP_SESSION_TIMEOUT_MINUTES__: $(__VUE_APP_SESSION_TIMEOUT_MINUTES__)
indexDeployVariables:
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
__VUE_APP_SOLARWINDS_MONITORING_SCRIPT__: $(__VUE_APP_SOLARWINDS_MONITORING_SCRIPT__)
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps - template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
parameters: parameters:
awsCliContainer: awscli awsCliContainer: awscli
@ -171,18 +159,6 @@ stages:
clearFolder: true clearFolder: true
deployFolder: "" deployFolder: ""
region: us-east-2 region: us-east-2
appDeployVariables:
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
__VUE_APP_HERITAGE_FUNNEL__: $(__VUE_APP_HERITAGE_FUNNEL__)
__VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__)
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
__VUE_APP_MY_ACCOUNT__: $(__VUE_APP_MY_ACCOUNT__)
__VUE_APP_SESSION_TIMEOUT_MINUTES__: $(__VUE_APP_SESSION_TIMEOUT_MINUTES__)
indexDeployVariables:
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
__VUE_APP_SOLARWINDS_MONITORING_SCRIPT__: $(__VUE_APP_SOLARWINDS_MONITORING_SCRIPT__)
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps - template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
parameters: parameters:
awsCliContainer: awscli awsCliContainer: awscli
@ -232,18 +208,6 @@ stages:
clearFolder: true clearFolder: true
deployFolder: "" deployFolder: ""
region: us-east-2 region: us-east-2
appDeployVariables:
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
__VUE_APP_HERITAGE_FUNNEL__: $(__VUE_APP_HERITAGE_FUNNEL__)
__VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__)
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
__VUE_APP_MY_ACCOUNT__: $(__VUE_APP_MY_ACCOUNT__)
__VUE_APP_SESSION_TIMEOUT_MINUTES__: $(__VUE_APP_SESSION_TIMEOUT_MINUTES__)
indexDeployVariables:
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
__VUE_APP_SOLARWINDS_MONITORING_SCRIPT__: $(__VUE_APP_SOLARWINDS_MONITORING_SCRIPT__)
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps - template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
parameters: parameters:
awsCliContainer: awscli awsCliContainer: awscli
@ -293,18 +257,6 @@ stages:
clearFolder: true clearFolder: true
deployFolder: "" deployFolder: ""
region: us-east-2 region: us-east-2
appDeployVariables:
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
__VUE_APP_HERITAGE_FUNNEL__: $(__VUE_APP_HERITAGE_FUNNEL__)
__VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__)
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
__VUE_APP_MY_ACCOUNT__: $(__VUE_APP_MY_ACCOUNT__)
__VUE_APP_SESSION_TIMEOUT_MINUTES__: $(__VUE_APP_SESSION_TIMEOUT_MINUTES__)
indexDeployVariables:
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
__VUE_APP_SOLARWINDS_MONITORING_SCRIPT__: $(__VUE_APP_SOLARWINDS_MONITORING_SCRIPT__)
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps - template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
parameters: parameters:
awsCliContainer: awscli awsCliContainer: awscli

23
environment-variables.js Normal file
View file

@ -0,0 +1,23 @@
// DEFINE every environment variable we will use here,
// with a default value to use in local development and unit tests.
//
// Default values will not be used when we deploy to prod or non-prod.
const environmentVariables = {
['__VUE_APP_CONSUMER_CF_DISTRO__']: "https://digitalapi.dev.safelite.io",
['__VUE_APP_CURRENT_ENVIRONMENT__']: "Localhost",
['__VUE_APP_GOOGLE_PLACES_API_KEY__']: "AIzaSyDptGCkOPgN2uWJOy4ou4M33phRD4MAoJo",
['__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__']: "https://www.googletagmanager.com/ns.html?id=GTM-M6XCRH&gtm_auth=amlAYNhxUxuskQo7jmjadg&gtm_preview=env-38&gtm_cookies_win=x",
['__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__']: "(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl+ '&gtm_auth=amlAYNhxUxuskQo7jmjadg&gtm_preview=env-38&gtm_cookies_win=x';f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-M6XCRH');",
['__VUE_APP_HERITAGE_FUNNEL__']: "https://fixmyglassdev.safelite.com/default.aspx",
['__VUE_APP_MY_ACCOUNT__']: "https://myaccountdev.safelite.com/",
['__VUE_APP_SAFELITE_HOP__']: "https://sv2-safelitehop-dev.safelite.com/fmgCheckoutShared.aspx",
['__VUE_APP_SESSION_TIMEOUT_MINUTES__']: 30,
['__VUE_APP_SOLARWINDS_MONITORING_SCRIPT__']: "",
};
const keysOnly = Object.keys(environmentVariables);
module.exports = {
environmentVariablesWithoutDefaults: keysOnly,
environmentVariablesWithDefaults: environmentVariables,
};

View file

@ -1,3 +1,5 @@
const { environmentVariablesWithDefaults } = require('./environment-variables.js');
module.exports = { module.exports = {
verbose: true, verbose: true,
coverageReporters: ["html", "text", "jest-junit", "cobertura"], reporters: ['default', 'jest-junit'], coverageReporters: ["html", "text", "jest-junit", "cobertura"], reporters: ['default', 'jest-junit'],
@ -49,3 +51,5 @@ module.exports = {
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit // Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit
//silent: true, //silent: true,
}; };
process.env = Object.assign(process.env, environmentVariablesWithDefaults);

View file

@ -4,7 +4,7 @@
<script> <script>
window.dataLayer = [{}]; window.dataLayer = [{}];
</script> </script>
<script><%= process.env.VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY %></script> <script><%= process.env.__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__ %></script>
<script src="https://applepay.cdn-apple.com/jsapi/v1.1.0/apple-pay-sdk.js"></script> <script src="https://applepay.cdn-apple.com/jsapi/v1.1.0/apple-pay-sdk.js"></script>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
@ -16,7 +16,7 @@
<!-- Google Tag Manager --> <!-- Google Tag Manager -->
<noscript> <noscript>
<iframe src="<%= process.env.VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC %>" <iframe src="<%= process.env.__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__ %>"
height="0" width="0" style="display:none;visibility:hidden"></iframe> height="0" width="0" style="display:none;visibility:hidden"></iframe>
</noscript> </noscript>
@ -27,6 +27,6 @@
<div id="app"></div> <div id="app"></div>
<!-- built files will be auto injected --> <!-- built files will be auto injected -->
<%= process.env.VUE_APP_SOLARWINDS_MONITORING_SCRIPT %> <%= process.env.__VUE_APP_SOLARWINDS_MONITORING_SCRIPT__ %>
</body> </body>
</html> </html>

View file

@ -1,12 +1,12 @@
const applicationConfig = { const applicationConfig = {
CONSUMER_CF_DISTRO: process.env.VUE_APP_CONSUMER_CF_DISTRO, CONSUMER_CF_DISTRO: process.env.__VUE_APP_CONSUMER_CF_DISTRO__,
GOOGLE_PLACES_API_KEY: process.env.VUE_APP_GOOGLE_PLACES_API_KEY, GOOGLE_PLACES_API_KEY: process.env.__VUE_APP_GOOGLE_PLACES_API_KEY__,
ANALYTICS_SESSION_TIMEOUT_MINUTES: 30, ANALYTICS_SESSION_TIMEOUT_MINUTES: 30,
SAVED_SESSION_TIMEOUT_DAYS: 45, SAVED_SESSION_TIMEOUT_DAYS: 45,
COOKIE_PATH: "/", COOKIE_PATH: "/",
CURRENT_ENVIRONMENT: process.env.VUE_APP_CURRENT_ENVIRONMENT, // "Localhost", "Dev", "QA", and "Prod" CURRENT_ENVIRONMENT: process.env.__VUE_APP_CURRENT_ENVIRONMENT__, // "Localhost", "Dev", "QA", and "Prod"
HERITAGE_FUNNEL: process.env.VUE_APP_HERITAGE_FUNNEL, HERITAGE_FUNNEL: process.env.__VUE_APP_HERITAGE_FUNNEL__,
SAFELITE_HOP: process.env.VUE_APP_SAFELITE_HOP, SAFELITE_HOP: process.env.__VUE_APP_SAFELITE_HOP__,
APPLICATION_NAME: "FixMyGlass", APPLICATION_NAME: "FixMyGlass",
ANALYTICS_APPLICATION_NAME: "FixMyGlassNextGen", ANALYTICS_APPLICATION_NAME: "FixMyGlassNextGen",
APPLICATION_ABBREVIATION: "fmg", APPLICATION_ABBREVIATION: "fmg",
@ -14,7 +14,7 @@ const applicationConfig = {
SITE_ENTRY_TRIGGER_VALUE: "FixMyGlass", SITE_ENTRY_TRIGGER_VALUE: "FixMyGlass",
CASH_PARENT_ACCOUNT_NUMBER: 167132, CASH_PARENT_ACCOUNT_NUMBER: 167132,
CASH_DEFAULT_BILL_TO_ACCOUNT_NUMBER: "87291", CASH_DEFAULT_BILL_TO_ACCOUNT_NUMBER: "87291",
MY_ACCOUNT: process.env.VUE_APP_MY_ACCOUNT, MY_ACCOUNT: process.env.__VUE_APP_MY_ACCOUNT__,
PIA_RESPONSE_URL: PIA_RESPONSE_URL:
location.protocol + "//" + location.host + "/fmg/payment-pia-return?src=concept-funnel", location.protocol + "//" + location.host + "/fmg/payment-pia-return?src=concept-funnel",
PIA_CANCEL_URL: PIA_CANCEL_URL:
@ -29,7 +29,7 @@ const applicationConfig = {
FRONTEND_LOGGER_PATH: "/analytics/api/v1/logging", FRONTEND_LOGGER_PATH: "/analytics/api/v1/logging",
AFFILIATE_COOKIE_CONTAINING_NAME: "_Track", AFFILIATE_COOKIE_CONTAINING_NAME: "_Track",
COOKIE_NAME_LENGTH_MAX_LIMIT: 44, COOKIE_NAME_LENGTH_MAX_LIMIT: 44,
SESSION_TIMEOUT_MINUTES: process.env.VUE_APP_SESSION_TIMEOUT_MINUTES, SESSION_TIMEOUT_MINUTES: process.env.__VUE_APP_SESSION_TIMEOUT_MINUTES__,
RETURN_USER_PAGE: "/fmg/return-user", RETURN_USER_PAGE: "/fmg/return-user",
}; };

View file

@ -2,16 +2,6 @@
<div <div
class="date-picker text-center" class="date-picker text-center"
:class="`${calendarViewDirection} calendar-2025 ${showPricingByDayClass}`"> :class="`${calendarViewDirection} calendar-2025 ${showPricingByDayClass}`">
<div class="date-picker-header">
<funnelSubHeader cmsWidgetName="DatePickerSubHeaderWidget" class="mt-5" />
<textBlock
v-show="durationTextBlockCopy"
:customText="durationTextBlockCopy"
typeStyle="small"
marginTopSizeOverride="0"
class="duration-text-block" />
</div>
<fieldset id="date-picker-fieldset" ref="datePickerFieldset"> <fieldset id="date-picker-fieldset" ref="datePickerFieldset">
<legend class="sr-only">Select a day and time</legend> <legend class="sr-only">Select a day and time</legend>
<div <div
@ -139,36 +129,11 @@
@click="showAnotherMonth"> @click="showAnotherMonth">
View more dates View more dates
</button> </button>
<timeSlotQuestion
ref="timeSlotModalQuestion"
v-model="selectedTimeSlotInfo"
cmsWidgetName="TimeSlotModalQuestion"
waitListLabelWidget="WaitListLabelWidget"
waitListQuestionWidget="WaitListQuestionWidget"
mobilePremiumCmsWidgetName="MobilePremiumTimeSlotModal"
mobileCmsWidgetName="MobileTimeSlotModal"
dropoffCmsWidgetName="DropOffTimeSlotModal"
sameDayDropOffCmsWidgetName="SameDayDropOffTimeSlotModal"
overnightDropOffCmsWidgetName="OvernightDropOffTimeSlotModal"
dropOffOrPickATimeQuestionCmsWidgetName="DropOffOrPickATimeQuestionWidget"
:selectedDate="selectedDate"
:appointmentType="appointmentType"
:premiumAppointmentFee="premiumAppointmentFee"
:displayWaitList="displayWaitList"
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
:estimatedServiceMinutesMinimum="estimatedServiceMinutesMinimum"
:estimatedServiceMinutesMaximum="estimatedServiceMinutesMaximum"
@waitListRequested="handleWaitListRequested"
@time-slot-modal-closed="timeSlotModalClosed" />
</fieldset> </fieldset>
</div> </div>
</template> </template>
<script> <script>
import timeSlotQuestion from "@/layouts/schedule/time-slot-question/time-slot-question.vue";
import textBlock from "@/digital-components/text-block/text-block";
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
// Supporting files // Supporting files
import loader from "@/ux-components/loader/loader"; import loader from "@/ux-components/loader/loader";
import store from "@/store"; import store from "@/store";
@ -178,12 +143,6 @@ import {
MONTHS_OF_YEAR, MONTHS_OF_YEAR,
DAYS_OF_WEEK, DAYS_OF_WEEK,
} from "@/digital-components/date-picker/mixins/constants"; } from "@/digital-components/date-picker/mixins/constants";
import {
AppointmentTypeStrings,
RouteCodeFlags,
PREMIUM_FEE_PART_TYPE,
cmsWidgetFieldMappings,
} from "@/constants/schedule-constants";
import { import {
selectableDaysOptions, selectableDaysOptions,
requiredParameter, requiredParameter,
@ -195,7 +154,6 @@ import {
import { useField, ErrorMessage } from "vee-validate"; import { useField, ErrorMessage } from "vee-validate";
import { deepClone } from "@/helpers/object-helper"; import { deepClone } from "@/helpers/object-helper";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helper";
export default { export default {
name: "datePicker", name: "datePicker",
@ -205,10 +163,8 @@ export default {
months: null, months: null,
disableViewMoreDatesButton: false, disableViewMoreDatesButton: false,
hideSomeDaysForInitialView: null, hideSomeDaysForInitialView: null,
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
selectableDatesInshop: [], // NOTE: this and the mobile version below use monthNum (1-based), NOT monthIndex (0-based) selectableDatesInshop: [], // NOTE: this and the mobile version below use monthNum (1-based), NOT monthIndex (0-based)
selectableDatesMobile: [], selectableDatesMobile: [],
durationTextBlockCopyForInshopOrDropoff: null,
}; };
}, },
props: { props: {
@ -242,12 +198,6 @@ export default {
pricingByDayBasePrice: Number, pricingByDayBasePrice: Number,
pricingByDayUpcharge: Number, pricingByDayUpcharge: Number,
isPricingByDayExperiment: Boolean, isPricingByDayExperiment: Boolean,
timeSlotsForSelectedDate: Object,
appointmentType: String,
premiumAppointmentFee: Object,
estimatedServiceMinutesMinimum: Number,
estimatedServiceMinutesMaximum: Number,
displayWaitList: Boolean,
isMobileSelected: Boolean, isMobileSelected: Boolean,
}, },
setup(props) { setup(props) {
@ -309,80 +259,10 @@ export default {
this.dispatchStoreAction(this.storeActions.SAVE_WAITLIST_REQUESTED, false, false); this.dispatchStoreAction(this.storeActions.SAVE_WAITLIST_REQUESTED, false, false);
}, },
}, },
dropOffDurationText() {
return this.getCmsContent("DropOffTimeSlotModal", cmsWidgetFieldMappings.DURATION);
},
sameDayDropoffDurationText() {
return this.getCmsContent(
"SameDayDropOffTimeSlotModal",
cmsWidgetFieldMappings.DURATION
);
},
overnightDropoffDurationText() {
return this.getCmsContent(
"OvernightDropOffTimeSlotModal",
cmsWidgetFieldMappings.DURATION
);
},
inshopDurationText() {
const inshopDurationTextWithoutTime = this.getCmsContent(
"TimeSlotModalQuestion",
cmsWidgetFieldMappings.DURATION
);
const inshopDurationTime = getDisplayTextForDurationLength(
this.estimatedServiceMinutesMinimum,
this.estimatedServiceMinutesMaximum
);
if (this.estimatedServiceMinutesMinimum && this.estimatedServiceMinutesMaximum) {
return `${inshopDurationTextWithoutTime} <b>${inshopDurationTime}</b>`;
}
return null;
},
mobileDurationText() {
const mobileDurationTextWithoutTime = this.getCmsContent(
"TimeSlotModalQuestion",
cmsWidgetFieldMappings.DURATION
);
const mobileDurationTime = getDisplayTextForDurationLength(
this.estimatedServiceMinutesMinimum,
this.estimatedServiceMinutesMaximum
);
if (this.estimatedServiceMinutesMinimum && this.estimatedServiceMinutesMaximum) {
return `${mobileDurationTextWithoutTime} <b>${mobileDurationTime}</b>`;
}
return null;
},
durationTextBlockCopy() {
if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
return this.mobileDurationText;
} else if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
return this.inshopDurationText;
} else if (
this.appointmentType === AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF ||
this.appointmentType === AppointmentTypeStrings.DROP_OFF
) {
return this.durationTextBlockCopyForInshopOrDropoff ?? this.inshopDurationText;
}
return null;
},
isSameDay() {
if (!this.timeSlotsForSelectedDate) {
return false;
}
const todaysDate = new Date().toISOString().split("T")[0];
return this.selectedDate === todaysDate;
},
}, },
methods: { methods: {
async initializeComponent(initialData) { async initializeComponent(initialData) {
await this.setCalendarData(initialData); await this.setCalendarData(initialData);
this.$refs.timeSlotModalQuestion.initializeComponent();
}, },
fireDateSelectedEvent(event, date) { fireDateSelectedEvent(event, date) {
// Ignore if arrow key selected radioButton // Ignore if arrow key selected radioButton
@ -949,42 +829,9 @@ export default {
}; };
window.requestAnimationFrame(step); window.requestAnimationFrame(step);
}, },
getSelectedTimeSlotInfo() {
const supportingItems = this.getSupportingItems();
var isPremiumAppointment = false;
if (supportingItems) {
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;
}, },
handleWaitListRequested(value) {
this.$emit("waitListRequested", value);
},
getDurationTextBlockCopyForInshopOrDropoff(selectedRouteCode) {
if (selectedRouteCode?.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
return this.overnightDropoffDurationText;
} else if (selectedRouteCode?.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
if (this.isSameDay) {
return this.sameDayDropoffDurationText;
} else {
return this.dropOffDurationText;
}
}
return this.inshopDurationText;
},
}, },
watch: { watch: {
modelValue(newValue) { modelValue(newValue) {
@ -998,19 +845,10 @@ export default {
this.scrollToElement("date-of-month-error"); this.scrollToElement("date-of-month-error");
} }
}, },
selectedTimeSlotInfo(newValue) {
const routeCode = newValue?.timeSlot?.routeCode;
this.durationTextBlockCopyForInshopOrDropoff =
this.getDurationTextBlockCopyForInshopOrDropoff(routeCode);
this.$emit("TimeSlotSelected", newValue); // needed to update footer button text on Schedule page and to save to Store correctly
},
}, },
components: { components: {
loader, loader,
ErrorMessage, ErrorMessage,
timeSlotQuestion,
textBlock,
funnelSubHeader,
}, },
}; };
</script> </script>
@ -1028,14 +866,6 @@ export default {
align-items: flex-start; align-items: flex-start;
text-align: left; text-align: left;
.duration-text-block {
text-align: left;
}
.date-picker-header {
margin-bottom: 1.5rem;
}
fieldset { fieldset {
flex-grow: 1; flex-grow: 1;
position: relative; position: relative;

View file

@ -195,6 +195,7 @@ export default {
name: "cart", name: "cart",
props: { props: {
modelValue: Object, modelValue: Object,
inactivePromos: Object,
damage: Object, damage: Object,
servicePackageOptionsCmsName: String, servicePackageOptionsCmsName: String,
availableVaps: Object, availableVaps: Object,
@ -327,15 +328,26 @@ export default {
this.lineItems.vaps = [...lineItems.vaps]; this.lineItems.vaps = [...lineItems.vaps];
this.lineItems.promos = [...lineItems.promos]; this.lineItems.promos = [...lineItems.promos];
await this.dispatchStoreAction(storeActions.SAVE_VAPS, this.lineItems.vaps, false); await this.dispatchStoreAction(storeActions.SAVE_VAPS, this.lineItems.vaps, false);
// any new promos that are also in inactive list?
const newInactivePromos = this.inactivePromos.filter((promo) => {
return this.lineItems.promos.includes(promo);
});
this.updateInactivePromos(newInactivePromos);
await this.dispatchStoreAction( await this.dispatchStoreAction(
storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS, storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
{ {
activePromos: this.lineItems.promos, activePromos: this.lineItems.promos,
inactivePromos: this.inactivePromos,
}, },
false false
); );
}, },
updateInactivePromos(newInactivePromos) {
this.$emit("update:inactivePromos", newInactivePromos);
},
getPromoCodeList() { getPromoCodeList() {
if (this.$refs["promoModalQuestion"]) { if (this.$refs["promoModalQuestion"]) {
return this.$refs["promoModalQuestion"].getPromoCodeList(); return this.$refs["promoModalQuestion"].getPromoCodeList();

View file

@ -42,12 +42,12 @@ export async function navigateToHeritageFunnel({ shouldSaveSession, pageNameToLo
} }
if ( if (
(process.env.VUE_APP_HERITAGE_FUNNEL.includes("fixmyglasstest") && (applicationConfig.HERITAGE_FUNNEL.includes("fixmyglasstest") &&
process.env.VUE_APP_CURRENT_ENVIRONMENT === "Localhost") || applicationConfig.CURRENT_ENVIRONMENT === "Localhost") ||
(process.env.VUE_APP_HERITAGE_FUNNEL.includes("fixmyglassdev") && (applicationConfig.HERITAGE_FUNNEL.includes("fixmyglassdev") &&
process.env.VUE_APP_CURRENT_ENVIRONMENT === "Localhost") || applicationConfig.CURRENT_ENVIRONMENT === "Localhost") ||
(process.env.VUE_APP_HERITAGE_FUNNEL.includes("localhost") && (applicationConfig.HERITAGE_FUNNEL.includes("localhost") &&
process.env.VUE_APP_CURRENT_ENVIRONMENT === "Localhost") applicationConfig.CURRENT_ENVIRONMENT === "Localhost")
) { ) {
heritageParms["forceEndToEndInsurance"] = true; heritageParms["forceEndToEndInsurance"] = true;
} }

View file

@ -6,6 +6,15 @@ 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 { experimentSettings } from "@/constants/experiments"; import { experimentSettings } from "@/constants/experiments";
import { storeMutations } from "@/constants/store-mutations";
import globalMethods from "@/global-methods";
globalMethods.callHttpClient = jest.fn();
global.crypto = { randomUUID: jest.fn() };
const mockPriceOrderItemsAndSaveServerData = jest
.fn()
.mockResolvedValue(/* your mock return value */);
// Mock our module for promises. // Mock our module for promises.
jest.mock("@/helpers/layout-helper.js", () => ({ jest.mock("@/helpers/layout-helper.js", () => ({
@ -34,7 +43,6 @@ describe("payment-method.vue", () => {
// Assert // Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled(); expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled();
}); });
test("if the continue button is clicked, navigate forward", async () => { test("if the continue button is clicked, navigate forward", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks(); const { wrapper } = setupMocks();
@ -81,7 +89,10 @@ function setupMocks() {
city: "Anytown", city: "Anytown",
state: "OH", state: "OH",
zipCode: "00000", zipCode: "00000",
provider: { providerNumber: "0000567" },
}, },
vehicle: { year: 2020, make: "Toyota", model: "Camry", carId: "12345" },
damage: { isRepair: false },
}, },
policy: { policy: {
currentDeductible: 123, currentDeductible: 123,
@ -101,6 +112,9 @@ function setupMocks() {
store: { store: {
getters: store.getters, getters: store.getters,
}, },
actions: {
priceOrderItemsAndSaveServerData: mockPriceOrderItemsAndSaveServerData,
},
}); });
const mockMixin = { const mockMixin = {

View file

@ -22,6 +22,7 @@
:availableVaps="availableVaps" :availableVaps="availableVaps"
:allowItemRemoval="true" :allowItemRemoval="true"
v-model="lineItems" v-model="lineItems"
v-model:inactivePromos="inactivePromos"
pageName="payment-method" pageName="payment-method"
servicePackageOptionsCmsName="ServicePackageTitle" servicePackageOptionsCmsName="ServicePackageTitle"
recyclingModalCmsWidgetName="RecycleModal" recyclingModalCmsWidgetName="RecycleModal"
@ -263,7 +264,7 @@ export default {
: (resultMap.wipers.filter((wiper) => wiper.partType == partTypeStrings.REAR_WIPER) ?? : (resultMap.wipers.filter((wiper) => wiper.partType == partTypeStrings.REAR_WIPER) ??
[]); []);
const lineItemsToTax = [ const allLineItems = [
resultMap.rainDefense, resultMap.rainDefense,
...supportingItems, ...supportingItems,
...availableFrontWipers, ...availableFrontWipers,
@ -272,6 +273,10 @@ export default {
...vaps, ...vaps,
]; ];
const lineItemsToTax = Array.from(
new Map(allLineItems.map((item) => [item.partNumber, item])).values()
);
const availableVaps = [ const availableVaps = [
resultMap.rainDefense, resultMap.rainDefense,
...availableFrontWipers, ...availableFrontWipers,
@ -542,6 +547,16 @@ export default {
getPromoCodeWithoutBundleIdentifier(x.promoCode) getPromoCodeWithoutBundleIdentifier(x.promoCode)
); );
// SAVE PROMO CHANGES TO STORE
this.dispatchStoreAction(
storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
{
activePromos: this.lineItems.promos,
inactivePromos: this.inactivePromos,
},
false
);
this.$refs.loadingModal.hideModal(); this.$refs.loadingModal.hideModal();
}, },
getIsRecalAckOptInFromStore() { getIsRecalAckOptInFromStore() {
@ -563,6 +578,7 @@ export default {
this.paymentMethod, this.paymentMethod,
false false
); );
// save lineitems as they now have salestax added // save lineitems as they now have salestax added
await this.dispatchStoreAction( await this.dispatchStoreAction(
storeActions.SAVE_GLASS_PARTS_SUPPRESSING_STATE_RESETTING, storeActions.SAVE_GLASS_PARTS_SUPPRESSING_STATE_RESETTING,

View file

@ -0,0 +1,114 @@
<template>
<textBlock
:customText="durationTextBlockCopy"
typeStyle="small"
marginTopSizeOverride="0"
class="duration-text-block" />
</template>
<script>
// Components
import textBlock from "@/digital-components/text-block/text-block";
// Constants
import { AppointmentTypeStrings, cmsWidgetFieldMappings } from "@/constants/schedule-constants";
// Helpers
import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helper";
export default {
name: "duration-text-block",
props: {
isSameDay: Boolean,
appointmentType: String,
estimatedServiceMinutesMinimum: String,
estimatedServiceMinutesMaximum: String,
isOvernightDropoff: Boolean,
},
data() {
return {
selectedRouteCode: null,
selectedAnswerForDropOffOrInshop: null,
selectedAnswerForTimeSlots: null,
};
},
computed: {
dropOffDurationText() {
return this.getCmsContent("DropOffTimeSlotModal", cmsWidgetFieldMappings.DURATION);
},
sameDayDropoffDurationText() {
return this.getCmsContent(
"SameDayDropOffTimeSlotModal",
cmsWidgetFieldMappings.DURATION
);
},
overnightDropoffDurationText() {
return this.getCmsContent(
"OvernightDropOffTimeSlotModal",
cmsWidgetFieldMappings.DURATION
);
},
inshopDurationText() {
const inshopDurationTextWithoutTime = this.getCmsContent(
"TimeSlotModalQuestion",
cmsWidgetFieldMappings.DURATION
);
const inshopDurationTime = getDisplayTextForDurationLength(
this.estimatedServiceMinutesMinimum,
this.estimatedServiceMinutesMaximum
);
if (this.estimatedServiceMinutesMinimum && this.estimatedServiceMinutesMaximum) {
return `${inshopDurationTextWithoutTime} <b>${inshopDurationTime}</b>`;
}
return null;
},
mobileDurationText() {
const mobileDurationTextWithoutTime = this.getCmsContent(
"TimeSlotModalQuestion",
cmsWidgetFieldMappings.DURATION
);
const mobileDurationTime = getDisplayTextForDurationLength(
this.estimatedServiceMinutesMinimum,
this.estimatedServiceMinutesMaximum
);
if (this.estimatedServiceMinutesMinimum && this.estimatedServiceMinutesMaximum) {
return `${mobileDurationTextWithoutTime} ${mobileDurationTime}`;
}
return null;
},
durationTextBlockCopy() {
if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
return this.mobileDurationText;
} else if (this.appointmentType === AppointmentTypeStrings.DROP_OFF) {
return this.getDurationTextBlockCopyForDropoff();
} else {
return this.inshopDurationText;
}
},
},
methods: {
getDurationTextBlockCopyForDropoff() {
if (this.isOvernightDropoff) {
return this.overnightDropoffDurationText;
} else {
if (this.isSameDay) {
return this.sameDayDropoffDurationText;
} else {
return this.dropOffDurationText;
}
}
},
},
components: {
textBlock,
},
};
</script>
<style></style>

View file

@ -0,0 +1,141 @@
<template>
<div
class="mobile-fee-waiver-alert-container py-3 px-4 my-2"
:class="[isExpanded ? 'expanded' : '']">
<div
class="mobile-fee-waiver-header d-flex align-items-center justify-content-between"
@click="toggleIsExpanded">
<span>
{{ headerText }}
</span>
</div>
<div class="mobile-fee-waiver-body">
<div class="mobile-fee-waiver-copy">
{{ subheaderText }}
</div>
<div class="mobile-fee-waiver-price-comp">
<span class="mobile-fee-waiver-strikethrough-price">
{{ priceStrikethroughText }}
</span>
<span class="mobile-fee-waiver-free-price">{{ freePriceText }}</span>
</div>
</div>
</div>
</template>
<script>
export default {
name: "mobile-fee-waiver-alert",
props: {
cmsWidgetName: String,
},
data() {
return {
isExpanded: true,
};
},
computed: {
headerText() {
return this.getCmsContent(this.cmsWidgetName, "HeaderText");
},
subheaderText() {
return this.getCmsContent(this.cmsWidgetName, "SubheaderText");
},
priceStrikethroughText() {
return this.getCmsContent(this.cmsWidgetName, "FooterText");
},
freePriceText() {
return this.getCmsContent(this.cmsWidgetName, "FooterText2");
},
},
methods: {
toggleIsExpanded() {
this.setIsExpanded(!this.isExpanded);
},
setIsExpanded(newVal) {
this.isExpanded = newVal;
},
},
components: {},
};
</script>
<style lang="scss">
.mobile-fee-waiver-alert-container {
background: $green-100;
border-radius: 0.25em;
border: 1px solid $green-400;
.mobile-fee-waiver-header {
font-weight: bold;
span {
flex-grow: 1;
color: $black;
}
&::before {
content: "";
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath fill='%230C7E47' d='M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0Zm3.7 6.171-4.449 4.45a.559.559 0 0 1-.8 0l-2.16-2.16a.563.563 0 0 1 .792-.8l1.76 1.76 4.066-4.042a.563.563 0 1 1 .792.8v-.008Z'/%3E%3C/svg%3E");
width: 1rem;
height: 1rem;
display: inline-block;
margin-right: 0.5rem;
}
&::after {
content: "";
transition: all 0.5s ease;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 9'%3E%3Cpath d='M7.99282 0.117702C7.75716 0.116049 7.53044 0.207797 7.3623 0.37286L0.255553 7.46878C0.0862976 7.63796 -0.00878906 7.86742 -0.00878906 8.10667C-0.00878906 8.34593 0.0862976 8.57539 0.255553 8.74457C0.424808 8.91374 0.654368 9.00879 0.893731 9.00879C1.13309 9.00879 1.36265 8.91374 1.53191 8.74457L7.99282 2.27378L14.4614 8.74457C14.6307 8.91205 14.8595 9.00547 15.0977 9.00428C15.3359 9.00308 15.5638 8.90737 15.7314 8.73819C15.8989 8.56901 15.9924 8.34022 15.9912 8.10216C15.99 7.8641 15.8942 7.63627 15.725 7.46878L8.6259 0.377963C8.45765 0.21088 8.22999 0.117289 7.99282 0.117702Z' fill='%230C7E47'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center center;
width: 16px;
height: 16px;
display: inline-flex;
position: relative;
}
}
.mobile-fee-waiver-body {
transition: all 500ms ease-out;
visibility: hidden;
overflow: hidden;
max-height: 0;
.mobile-fee-waiver-price-comp {
margin-top: 0.5em;
.mobile-fee-waiver-strikethrough-price {
font-size: smaller;
text-decoration: line-through;
}
.mobile-fee-waiver-free-price {
display: inline-block;
font-weight: bolder;
border-radius: 1em;
background-color: $green-300;
padding: 0 0.5em;
margin-inline-start: 0.5em;
}
}
}
&.expanded {
.mobile-fee-waiver-header {
&::after {
transform: rotate(180deg);
}
}
.mobile-fee-waiver-body {
visibility: visible;
max-height: none;
border-top: 1px solid $green-400;
padding-top: 0.5em;
margin-top: 0.5em;
}
}
}
</style>

View file

@ -5,17 +5,16 @@
<div class="container page-container-grouped-styles"> <div class="container page-container-grouped-styles">
<div class="row"> <div class="row">
<div class="col-12 col-md-10 col-lg-8 col-xl-7"> <div class="col-12 col-md-10 col-lg-8 col-xl-7">
<alert <mobileFeeWaiverAlert
ref="alertMobileFeeFree" cmsWidgetName="MobileFeeFreeDropdownWidget"
class="mb-5" v-if="showMobileFreeAlert" />
cmsWidgetName="AlertMobileFeeFreeWidget"
v-if="showMobileFreeAlert"
alertClass="alert-success" />
</div> </div>
</div> </div>
<div class="row appointment-type"> <div class="row appointment-type">
<div class="col-12 col-md-10 col-lg-8 col-xl-7"> <div class="col-12 col-md-10 col-lg-8 col-xl-7">
<funnelSubHeader cmsWidgetName="ScheduleYourServiceWidget" class="mb-5" /> <funnelSubHeader
cmsWidgetName="ScheduleYourServiceWidget"
id="schedule-your-service" />
<appointmentTypeQuestion <appointmentTypeQuestion
v-model="appointmentTypeFromAppointmentTypeQuestion" v-model="appointmentTypeFromAppointmentTypeQuestion"
v-show="isAppointmentTypeDisplayed" v-show="isAppointmentTypeDisplayed"
@ -118,6 +117,15 @@
<div class="row date-picker-wrapper"> <div class="row date-picker-wrapper">
<div class="col-12 col-md-10 col-lg-8 col-xl-7"> <div class="col-12 col-md-10 col-lg-8 col-xl-7">
<locationAlerts cmsWidgetPrefix="LocationAlert-" ref="locationAlerts" /> <locationAlerts cmsWidgetPrefix="LocationAlert-" ref="locationAlerts" />
<div v-show="appointmentType" class="date-picker-header fw-normal mt-5 mb-5">
<textBlock :customText="datePickerHeaderText" typeStyle="button-question" />
<durationTextBlock
:isSameDay="isSameDay"
:appointmentType="appointmentType"
:estimatedServiceMinutesMinimum="estimatedServiceMinutesMinimum"
:estimatedServiceMinutesMaximum="estimatedServiceMinutesMaximum"
:isOvernightDropoff="isOvernightDropoff" />
</div>
<datePicker <datePicker
:currentZip="zipCode" :currentZip="zipCode"
:currentProviderNumber="selectedProvider?.providerNumber" :currentProviderNumber="selectedProvider?.providerNumber"
@ -134,14 +142,25 @@
:pricingByDayBasePrice="pricingByDayBasePrice" :pricingByDayBasePrice="pricingByDayBasePrice"
:pricingByDayUpcharge="pricingByDayUpcharge" :pricingByDayUpcharge="pricingByDayUpcharge"
:showPricingByDay="showPricingByDay" :showPricingByDay="showPricingByDay"
:isPricingByDayExperiment="isPricingByDayExperiment" :isPricingByDayExperiment="isPricingByDayExperiment" />
:timeSlotsForSelectedDate="timeSlotsForSelectedDate" <timeSlotQuestion
ref="timeSlotModalQuestion"
v-if="showTimeSlotQuestion"
@time-slot-selection-changed="updateTimeSlot"
waitListLabelWidget="WaitListLabelWidget"
waitListQuestionWidget="WaitListQuestionWidget"
mobilePremiumCmsWidgetName="MobilePremiumTimeSlotModal"
mobileCmsWidgetName="MobileTimeSlotModal"
dropoffCmsWidgetName="DropOffTimeSlotModal"
sameDayDropOffCmsWidgetName="SameDayDropOffTimeSlotModal"
overnightDropOffCmsWidgetName="OvernightDropOffTimeSlotModal"
dropOffOrPickATimeQuestionCmsWidgetName="DropOffOrPickATimeQuestionWidget"
:appointmentType="appointmentType" :appointmentType="appointmentType"
:premiumAppointmentFee="mobilePremiumAppointmentFee" :premiumAppointmentFee="mobilePremiumAppointmentFee"
:estimatedServiceMinutesMinimum="getServiceMinutesMin"
:estimatedServiceMinutesMaximum="getServiceMinutesMax"
@timeSlotSelected="updateTimeSlot"
:displayWaitList="displayWaitList" :displayWaitList="displayWaitList"
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
:isSameDay="isSameDay"
:selectedRouteCodeData="selectedRouteCodeData"
@waitListRequested="handleWaitListRequested" /> @waitListRequested="handleWaitListRequested" />
<navbar <navbar
cmsWidgetName="FunnelFooterWidget" cmsWidgetName="FunnelFooterWidget"
@ -160,9 +179,14 @@
import alert from "@/ux-components/alert/alert"; import alert from "@/ux-components/alert/alert";
import serviceZipModalQuestion from "@/layouts/service-location/service-zip-modal-question/service-zip-modal-question"; import serviceZipModalQuestion from "@/layouts/service-location/service-zip-modal-question/service-zip-modal-question";
import appointmentTypeQuestion from "@/layouts/service-location/appointment-type-question/appointment-type-question"; import appointmentTypeQuestion from "@/layouts/service-location/appointment-type-question/appointment-type-question";
import shopQuestion from "@/layouts/service-location/shop-question/shop-question";
import shopListButton from "@/layouts/service-location/shop-question/shop-list-button/shop-list-button"; import shopListButton from "@/layouts/service-location/shop-question/shop-list-button/shop-list-button";
import shopLocation from "@/layouts/schedule/shop-location/shop-location"; import shopLocation from "@/layouts/schedule/shop-location/shop-location";
import timeSlotQuestion from "@/layouts/schedule/time-slot-question/time-slot-question.vue";
import durationTextBlock from "@/layouts/schedule/duration-text-block/duration-text-block.vue";
import shopQuestion from "@/layouts/service-location/shop-question/shop-question";
import shopQuestionPopup from "@/layouts/service-location/shop-question/shop-question-popup";
import buttonQuestion from "@/digital-components/button-question/button-question.vue";
import funnelHeader from "@/fmg-components/funnel-header/funnel-header"; import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
import navbar from "@/fmg-components/nav-bar/nav-bar"; import navbar from "@/fmg-components/nav-bar/nav-bar";
@ -174,6 +198,7 @@ import datePicker from "@/digital-components/date-picker/date-picker";
import locationAlerts from "@/layouts/schedule/location-alerts/location-alerts"; import locationAlerts from "@/layouts/schedule/location-alerts/location-alerts";
import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal"; import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal";
import textBlock from "@/digital-components/text-block/text-block"; import textBlock from "@/digital-components/text-block/text-block";
import mobileFeeWaiverAlert from "./mobile-fee-waiver-alert/mobile-fee-waiver-alert.vue";
// Supporting files // Supporting files
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
@ -626,6 +651,16 @@ export default {
return this.isGlassServiceableMobile; return this.isGlassServiceableMobile;
} }
}, },
showTimeSlotQuestion() {
if (
this.selectedDate &&
this.selectedDate.includes("mobile") &&
!this.isMobileSelected
) {
this.getNewSelectedDate();
}
return this.selectedDate && this.appointmentTypeFromAppointmentTypeQuestion;
},
isMobileStaticRecalibrationApplicable() { isMobileStaticRecalibrationApplicable() {
return ( return (
this.displayMSR && this.displayMSR &&
@ -785,6 +820,21 @@ export default {
} }
return []; return [];
}, },
selectedRouteCodeData() {
if (!this.selectedTimeSlotInfo?.timeSlot?.routeCode) {
return null;
}
return {
routeCode: this.selectedTimeSlotInfo.timeSlot.routeCode,
isPremiumAppointment: this.selectedTimeSlotInfo.isPremiumAppointment,
};
},
questionText() {
return this.getCmsContent("YourSafeliteShopWidget", "QuestionText");
},
datePickerHeaderText() {
return this.getCmsContent("DatePickerSubHeaderWidget", "HeaderText");
},
appointmentTypeStrings() { appointmentTypeStrings() {
return AppointmentTypeStrings; return AppointmentTypeStrings;
}, },
@ -795,12 +845,12 @@ export default {
// Splits content when brackets are found in text so that text can be looped through and router-link can be injected when needed // Splits content when brackets are found in text so that text can be looped through and router-link can be injected when needed
return this.splitCopyOnCMSPlaceHolder(this.ChangeShopLinkText); return this.splitCopyOnCMSPlaceHolder(this.ChangeShopLinkText);
}, },
getServiceMinutesMin() { estimatedServiceMinutesMinimum() {
return this.isMobileSelected return this.isMobileSelected
? this.selectableDatesMobile.estimatedServiceMinutesMinimum ? this.selectableDatesMobile.estimatedServiceMinutesMinimum
: this.selectableDatesInshop.estimatedServiceMinutesMinimum; : this.selectableDatesInshop.estimatedServiceMinutesMinimum;
}, },
getServiceMinutesMax() { estimatedServiceMinutesMaximum() {
return this.isMobileSelected return this.isMobileSelected
? this.selectableDatesMobile.estimatedServiceMinutesMaximum ? this.selectableDatesMobile.estimatedServiceMinutesMaximum
: this.selectableDatesInshop.estimatedServiceMinutesMaximum; : this.selectableDatesInshop.estimatedServiceMinutesMaximum;
@ -820,6 +870,18 @@ export default {
); );
} }
}, },
isSameDay() {
const todaysDate = new Date().toISOString().split("T")[0];
return this.selectedDate === todaysDate;
},
isOvernightDropoff() {
return (
this.selectedTimeSlotInfo?.timeSlot?.routeCode &&
this.selectedTimeSlotInfo.timeSlot.routeCode.includes(
RouteCodeFlags.OVERNIGHT_DROP_OFF
)
);
},
}, },
methods: { methods: {
splitCopyOnCMSPlaceHolder, splitCopyOnCMSPlaceHolder,
@ -1558,22 +1620,46 @@ export default {
} }
}, },
updateTimeSlot(timeSlotObj) { updateTimeSlot(timeSlotObj) {
if (this.preSelectedDate && !timeSlotObj?.timeSlot?.date) return; if (!timeSlotObj?.routeCode) {
this.selectedTimeSlotInfo = timeSlotObj; this.appointmentType = AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF;
this.selectedTimeSlotInfo = {
timeSlot: {
date: null,
routeCode: null,
startTime: null,
endTime: null,
jobMaxMinutes: null,
jobMinMinutes: null,
},
isPremiumAppointment: null,
};
return;
}
const timeSlot = this.timeSlotsForSelectedDate?.timeSlots?.find(
(slot) => slot.id == timeSlotObj.routeCode
);
this.selectedTimeSlotInfo = {
timeSlot: {
date: this.timeSlotsForSelectedDate.date,
routeCode: timeSlotObj.routeCode,
startTime: timeSlot.startTime,
endTime: timeSlot.endTime,
jobMaxMinutes: this.estimatedServiceMinutesMaximum.toString(),
jobMinMinutes: this.estimatedServiceMinutesMinimum.toString(),
},
isPremiumAppointment: timeSlotObj.isPremiumAppointment ? true : false,
};
if ( if (
this.appointmentType !== AppointmentTypeStrings.MOBILE && this.appointmentType !== AppointmentTypeStrings.MOBILE &&
this.appointmentType !== null this.appointmentType !== null
) { ) {
// update apptType based on routeCode to determine if it should be dropoff or inshop // update apptType based on routeCode to determine if it should be dropoff or inshop
debugLog(`Updating appointment type based on route code.`); debugLog(`Updating appointment type based on route code.`);
debugLog(`Route code =`, timeSlotObj.timeSlot.routeCode); debugLog(`Route code =`, timeSlotObj.routeCode);
debugLog( debugLog(`Parsed type = `, this.getInShopOrDropOffApptType(timeSlotObj.routeCode));
`Parsed type = `, this.appointmentType = this.getInShopOrDropOffApptType(timeSlotObj.routeCode);
this.getInShopOrDropOffApptType(timeSlotObj.timeSlot.routeCode)
);
this.appointmentType = this.getInShopOrDropOffApptType(
timeSlotObj.timeSlot.routeCode
);
} }
}, },
getInShopOrDropOffApptType(routeCode) { getInShopOrDropOffApptType(routeCode) {
@ -1607,8 +1693,13 @@ export default {
}, },
updateSelectedProvider(newProvider) { updateSelectedProvider(newProvider) {
if (newProvider) { if (newProvider) {
const didProviderNumberChange =
this.selectedProvider.providerNumber !== newProvider.providerNumber;
this.selectedProvider = newProvider; this.selectedProvider = newProvider;
this.initializeDatePicker();
if (didProviderNumberChange) {
this.initializeDatePicker();
}
} else if (this.appointmentType === this.appointmentTypeStrings.MOBILE) { } else if (this.appointmentType === this.appointmentTypeStrings.MOBILE) {
this.selectedProvider = { this.selectedProvider = {
providerNumber: this.shopProviderData.mobileProviderNumber.toString(), providerNumber: this.shopProviderData.mobileProviderNumber.toString(),
@ -1697,6 +1788,9 @@ export default {
this.selectedDate = this.getSelectedDate(); this.selectedDate = this.getSelectedDate();
this.setDisplayWaitList(); this.setDisplayWaitList();
}, },
getNewSelectedDate() {
this.selectedDate = this.getSelectedDate();
},
}, },
watch: { watch: {
appointmentTypeFromAppointmentTypeQuestion: { appointmentTypeFromAppointmentTypeQuestion: {
@ -1740,12 +1834,15 @@ export default {
datePicker, datePicker,
locationAlerts, locationAlerts,
textBlock, textBlock,
timeSlotQuestion,
alert, alert,
serviceZipModalQuestion, serviceZipModalQuestion,
appointmentTypeQuestion, appointmentTypeQuestion,
contentGroupModal, contentGroupModal,
durationTextBlock,
shopLocation, shopLocation,
mobileFeeWaiverAlert,
}, },
}; };
</script> </script>
@ -1780,12 +1877,17 @@ export default {
font-weight: 600; font-weight: 600;
} }
:deep(.funnel-sub-header:not(.mb-5)) { :deep(.funnel-sub-header:not(#schedule-your-service)) {
h5.dark-header { h5.dark-header {
font-weight: 600; font-weight: 600;
font-size: 1rem; font-size: 1rem;
} }
} }
:deep(.funnel-sub-header#schedule-your-service) {
h5.dark-header {
margin-bottom: 0;
}
}
.col-12 a { .col-12 a {
font-family: UrbanistSemibold; font-family: UrbanistSemibold;

View file

@ -3,6 +3,7 @@
<buttonQuestion <buttonQuestion
v-if="isDropOffAppointmentAvailable" v-if="isDropOffAppointmentAvailable"
v-model="selectedAnswerForDropOffOrInshop" v-model="selectedAnswerForDropOffOrInshop"
@update:modelValue="dropOffSelectionChanged"
:questionText="dropOffOrPickATimeQuestionLabelText" :questionText="dropOffOrPickATimeQuestionLabelText"
:questionTextDescription="dropOffOrPickATimeQuestionDescription" :questionTextDescription="dropOffOrPickATimeQuestionDescription"
:answers="answersForDropOffQuestion" :answers="answersForDropOffQuestion"
@ -24,6 +25,7 @@
<buttonQuestion <buttonQuestion
ref="buttonQuestion" ref="buttonQuestion"
v-if="shouldDisplayTimeSlotQuestion" v-if="shouldDisplayTimeSlotQuestion"
@update:modelValue="timeSlotSelectionChanged"
buttonTypeString="timeSlotModalListButton" buttonTypeString="timeSlotModalListButton"
:buttonTypeObject="timeSlotModalListButton" :buttonTypeObject="timeSlotModalListButton"
class="mt-4 timeslots" class="mt-4 timeslots"
@ -44,7 +46,7 @@
v-if="supplementalInformationBlock" v-if="supplementalInformationBlock"
v-html="supplementalInformationBlock" /> v-html="supplementalInformationBlock" />
<div <div
v-if="hasWaitListExperiment && displayWaitListFeature && selectedDate" v-if="hasWaitListExperiment && displayWaitListFeature"
class="mt-5 bg-light rounded waitlist"> class="mt-5 bg-light rounded waitlist">
<textBlock <textBlock
cmsWidgetName="WaitListLabelWidget" cmsWidgetName="WaitListLabelWidget"
@ -59,7 +61,7 @@
@click="waitListChecked" /> @click="waitListChecked" />
</div> </div>
<div <div
v-if="waitListRequested && displayWaitList && selectedDate" v-if="waitListRequested && displayWaitList"
class="rounded waitlist-success" class="rounded waitlist-success"
ref="waitlistSuccessMessage"> ref="waitlistSuccessMessage">
<img :src="waitListSuccessImage" class="success-image" /> <img :src="waitListSuccessImage" class="success-image" />
@ -92,7 +94,6 @@ import {
isDropOffRouteCode, isDropOffRouteCode,
militaryToTwelveHourTime, militaryToTwelveHourTime,
} from "@/layouts/schedule/helpers/schedule-helper"; } from "@/layouts/schedule/helpers/schedule-helper";
import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helper";
// Validation // Validation
import { defineRule, useField } from "vee-validate"; import { defineRule, useField } from "vee-validate";
@ -116,23 +117,8 @@ defineRule("time-slot-required", required(errorMessages.OPTION_REQUIRED));
export default { export default {
name: "time-slot-question", name: "time-slot-question",
emits: ["update:modelValue", "TimeSlotSelected", "click-event"], emits: ["update:modelValue", "time-slot-selection-changed"],
props: { props: {
modelValue: {
type: Object,
default: () => ({
timeSlot: {
routeCode: null,
date: null,
startTime: null,
endTime: null,
jobMaxMinutes: null,
jobMinMinutes: null,
},
isPremiumAppointment: null,
}),
},
cmsWidgetName: String,
mobileCmsWidgetName: String, mobileCmsWidgetName: String,
mobilePremiumCmsWidgetName: String, mobilePremiumCmsWidgetName: String,
dropoffCmsWidgetName: String, dropoffCmsWidgetName: String,
@ -142,21 +128,23 @@ export default {
appointmentType: String, appointmentType: String,
timeSlotsForSelectedDate: Object, timeSlotsForSelectedDate: Object,
premiumAppointmentFee: Object, premiumAppointmentFee: Object,
estimatedServiceMinutesMinimum: Number,
estimatedServiceMinutesMaximum: Number,
validationRules: String, validationRules: String,
customComponentId: String, customComponentId: String,
selectedDate: String, isSameDay: Boolean,
displayWaitList: Boolean, displayWaitList: Boolean,
selectedRouteCodeData: Object,
}, },
data() { data() {
return { return {
selectedRouteCode: null,
timeSlotModalListButton: timeSlotModalListButton, timeSlotModalListButton: timeSlotModalListButton,
selectedAnswerForDropOffOrInshop: null,
selectedAnswerForTimeSlots: null, selectedAnswerForTimeSlots: null,
selectedAnswerForDropOffOrInshop: null,
}; };
}, },
mounted() {
this.updateDropoffAndTimeSlotAnswersFromSelectedRouteCodeData(this.selectedRouteCodeData);
this.autoSelectTimeSlotIfOnlyOneIsAvailable();
},
setup(props) { setup(props) {
const uuid = uuidv4(); const uuid = uuidv4();
const componentId = !props.customComponentId const componentId = !props.customComponentId
@ -186,20 +174,33 @@ export default {
errors, errors,
}; };
}, },
watch: {
waitListRequested(newVal) {
if (newVal) {
this.$nextTick(() => {
this.scrollToSuccessMessage();
});
}
},
selectedRouteCodeData(newValue) {
this.updateDropoffAndTimeSlotAnswersFromSelectedRouteCodeData(newValue);
},
timeSlotsForSelectedDate() {
this.selectedAnswerForDropOffOrInshop = null;
this.selectedAnswerForTimeSlots = null;
this.autoSelectTimeSlotIfOnlyOneIsAvailable();
},
},
computed: { computed: {
supplementalInformationBlock() { supplementalInformationBlock() {
let appointmentTypeCmsWidgetName; let appointmentTypeCmsWidgetName;
if ( if (
this.selectedDate == null ||
this.appointmentType === AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF || this.appointmentType === AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF ||
this.appointmentType === AppointmentTypeStrings.IN_SHOP || this.appointmentType === AppointmentTypeStrings.IN_SHOP ||
this.appointmentType === AppointmentTypeStrings.DROP_OFF || this.appointmentType === AppointmentTypeStrings.DROP_OFF ||
!this.availableTimeSlots !this.availableTimeSlots
) { ) {
if (!this.selectedAnswerForTimeSlots && !this.selectedAnswerForDropOffOrInshop) {
this.autoSelectTimeSlotIfOnlyOneIsAvailable();
}
return null; return null;
// This is planned to be used again for drop-off // This is planned to be used again for drop-off
// Wrote this to be ready for that eventuality, is untested and no HTML work done yet // Wrote this to be ready for that eventuality, is untested and no HTML work done yet
@ -214,11 +215,7 @@ export default {
// ); // );
// } // }
} else { } else {
if (!this.selectedAnswerForTimeSlots && this.selectedRouteCode) { appointmentTypeCmsWidgetName = this.selectedRouteCodeData?.routeCode?.includes(
// Clearing selectedRouteCode if no time slot is selected
this.resetSelectedTimeSlot();
}
appointmentTypeCmsWidgetName = this.selectedRouteCode?.includes(
PREMIUM_TIME_SLOT_ID_FLAG PREMIUM_TIME_SLOT_ID_FLAG
) )
? this.mobilePremiumCmsWidgetName ? this.mobilePremiumCmsWidgetName
@ -299,13 +296,6 @@ export default {
// return null; // return null;
// } // }
// }, // },
isSameDay() {
if (!this.timeSlotsForSelectedDate) {
return false;
}
const todaysDate = new Date().toISOString().split("T")[0];
return this.selectedDate === todaysDate;
},
availableTimeSlots() { availableTimeSlots() {
if (!this.timeSlotsForSelectedDate) { if (!this.timeSlotsForSelectedDate) {
return null; return null;
@ -407,29 +397,60 @@ export default {
}, },
}, },
methods: { methods: {
initializeComponent() { updateDropoffAndTimeSlotAnswersFromSelectedRouteCodeData(selectedRouteCodeData) {
this.setSelectedRouteCodeFromParent(); if (selectedRouteCodeData?.routeCode) {
}, let routeCode = selectedRouteCodeData.routeCode;
async setSelectedTimeSlot() { if (isDropOffRouteCode(routeCode)) {
this.selectedRouteCode = this.selectedAnswerForDropOffOrInshop = routeCode;
this.selectedAnswerForTimeSlots || this.selectedAnswerForDropOffOrInshop; } else {
this.$emit( this.selectedAnswerForDropOffOrInshop = PICK_A_TIME_BUTTON_VALUE;
"update:modelValue", if (this.selectedRouteCodeData.isPremiumAppointment) {
this.getSelectedTimeSlotInfoObject(this.selectedRouteCode) routeCode = this.addPremiumFlagToInput(routeCode);
); }
}, this.selectedAnswerForTimeSlots = routeCode;
getRelevantDropOffCmsWidgetNameForSelectedTimeSlot( }
selectedRouteCode, } else {
isSameDayRelevant = false this.selectedAnswerForDropOffOrInshop = null;
) { this.selectedAnswerForTimeSlots = null;
if (selectedRouteCode.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
return this.overnightDropOffCmsWidgetName;
} else if (selectedRouteCode.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
return this.isSameDay && isSameDayRelevant
? this.sameDayDropOffCmsWidgetName
: this.dropoffCmsWidgetName;
} }
}, },
dropOffSelectionChanged(newValue) {
let newSelectedRouteCodeData = null;
if (newValue && newValue != PICK_A_TIME_BUTTON_VALUE) {
newSelectedRouteCodeData = {
routeCode: newValue,
isPremiumAppointment: false,
};
}
this.selectedAnswerForTimeSlots = null;
this.$emit("time-slot-selection-changed", newSelectedRouteCodeData);
},
timeSlotSelectionChanged(newValue) {
if (newValue) {
let routeCode = newValue;
let isPremiumAppointment = routeCode.includes(PREMIUM_TIME_SLOT_ID_FLAG);
if (isPremiumAppointment) {
routeCode = this.removePremiumFlagFromInput(routeCode);
}
this.$emit("time-slot-selection-changed", {
routeCode: routeCode,
isPremiumAppointment: isPremiumAppointment,
});
}
},
// This is unused but planned to be used again
// getRelevantDropOffCmsWidgetNameForSelectedTimeSlot(
// selectedRouteCode,
// isSameDayRelevant = false
// ) {
// if (selectedRouteCode.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
// return this.overnightDropOffCmsWidgetName;
// } else if (selectedRouteCode.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
// return this.isSameDay && isSameDayRelevant
// ? this.sameDayDropOffCmsWidgetName
// : this.dropoffCmsWidgetName;
// }
// },
getAvailableTimeSlotsForInshop(timeSlotsForSelectedDate) { getAvailableTimeSlotsForInshop(timeSlotsForSelectedDate) {
return timeSlotsForSelectedDate return timeSlotsForSelectedDate
.filter((timeSlot) => { .filter((timeSlot) => {
@ -463,8 +484,6 @@ export default {
); );
} }
this.resetSelectedTimeSlotIfDateChange(timeSlotsForSelectedDate);
return availableTimeSlots; return availableTimeSlots;
}, },
getPremiumAppointmentTimeSlot(timeSlotData) { getPremiumAppointmentTimeSlot(timeSlotData) {
@ -480,35 +499,19 @@ export default {
}, },
}; };
}, },
setSelectedRouteCodeFromParent() {
if (!this.modelValue?.timeSlot?.routeCode) {
return;
}
const routeCodeFromParent = this.modelValue.timeSlot.routeCode;
if (this.modelValue?.isPremiumAppointment) {
// Mobile Only
this.selectedAnswerForTimeSlots = this.addPremiumFlagToInput(routeCodeFromParent);
} else {
if (isDropOffRouteCode(routeCodeFromParent)) {
this.selectedAnswerForDropOffOrInshop = routeCodeFromParent;
} else {
this.selectedAnswerForDropOffOrInshop = PICK_A_TIME_BUTTON_VALUE;
this.selectedAnswerForTimeSlots = routeCodeFromParent;
}
}
},
getWaitListRequestedFromStore() { getWaitListRequestedFromStore() {
return store.getters.order.customer?.waitListRequested; return store.getters.order.customer?.waitListRequested;
}, },
autoSelectTimeSlotIfOnlyOneIsAvailable() { autoSelectTimeSlotIfOnlyOneIsAvailable() {
const numberOfOptions = this.timeSlotsForSelectedDate?.timeSlots?.length; const numberOfOptions = this.timeSlotsForSelectedDate?.timeSlots?.length;
if (this.appointmentType && numberOfOptions === 1) { if (numberOfOptions === 1) {
if (this.availableTimeSlots.length > 0) { if (this.availableTimeSlots.length > 0) {
// this.availableTimeSlots only returns mobile/inshop slots so we know
// the only available slot is not dropOFf
this.selectedAnswerForTimeSlots = this.availableTimeSlots[0].value; this.selectedAnswerForTimeSlots = this.availableTimeSlots[0].value;
} else { } else {
this.selectedAnswerForDropOffOrInshop = this.answersForDropOffQuestion[0].value; this.selectedAnswerForDropOffOrInshop = this.answersForDropOffQuestion[0].value;
} }
this.setSelectedTimeSlot();
} }
}, },
addPremiumFlagToInput(routeCode) { addPremiumFlagToInput(routeCode) {
@ -517,41 +520,6 @@ export default {
removePremiumFlagFromInput(routeCode) { removePremiumFlagFromInput(routeCode) {
return routeCode.replace(PREMIUM_TIME_SLOT_ID_FLAG, ""); return routeCode.replace(PREMIUM_TIME_SLOT_ID_FLAG, "");
}, },
getSelectedTimeSlotInfoObject(routeCode) {
const routeCodeIncludesPremium = routeCode?.includes(PREMIUM_TIME_SLOT_ID_FLAG);
if (routeCodeIncludesPremium) {
routeCode = this.removePremiumFlagFromInput(routeCode);
}
const timeSlot = this.timeSlotsForSelectedDate?.timeSlots?.find(
(slot) => slot.id == routeCode
);
if (timeSlot) {
return {
timeSlot: {
date: this.timeSlotsForSelectedDate.date,
routeCode: timeSlot.id,
startTime: timeSlot.startTime,
endTime: timeSlot.endTime,
jobMaxMinutes: this.estimatedServiceMinutesMaximum?.toString() || null,
jobMinMinutes: this.estimatedServiceMinutesMinimum?.toString() || null,
},
isPremiumAppointment: routeCodeIncludesPremium ? true : false,
};
}
return {
timeSlot: {
date: null,
routeCode: null,
startTime: null,
endTime: null,
jobMaxMinutes: null,
jobMinMinutes: null,
},
isPremiumAppointment: null,
};
},
waitListChecked(event) { waitListChecked(event) {
this.$emit("waitListRequested", event.target.checked); this.$emit("waitListRequested", event.target.checked);
}, },
@ -561,65 +529,6 @@ export default {
successMessageElement.scrollIntoView({ behavior: "smooth" }); successMessageElement.scrollIntoView({ behavior: "smooth" });
} }
}, },
resetSelectedTimeSlotIfDateChange(timeSlotsForSelectedDate) {
if (this.selectedRouteCode) {
const selectedRouteCodeString = this.selectedRouteCode.replace(
PREMIUM_TIME_SLOT_ID_FLAG,
""
);
const matchingTimeSlot = timeSlotsForSelectedDate.find((slot) => {
return slot.id === selectedRouteCodeString;
});
if (!matchingTimeSlot) {
this.resetSelectedTimeSlot();
}
}
},
resetSelectedTimeSlot() {
this.selectedRouteCode = null;
},
},
watch: {
waitListRequested(newVal) {
if (newVal) {
this.$nextTick(() => {
this.scrollToSuccessMessage();
});
}
},
modelValue: {
handler(newValue) {
this.handleChange(newValue);
},
deep: true,
},
selectedDate: {
handler() {
this.resetSelectedTimeSlot();
this.selectedAnswerForDropOffOrInshop = null;
this.selectedAnswerForTimeSlots = null;
this.autoSelectTimeSlotIfOnlyOneIsAvailable();
},
},
selectedRouteCode(newValue) {
if (newValue) {
this.setSelectedTimeSlot();
}
},
selectedAnswerForDropOffOrInshop(newValue) {
if (newValue == PICK_A_TIME_BUTTON_VALUE) {
this.selectedRouteCode = null;
this.setSelectedTimeSlot();
} else {
this.selectedAnswerForTimeSlots = null;
this.selectedRouteCode = newValue;
}
},
selectedAnswerForTimeSlots(newValue) {
if (newValue) {
this.selectedRouteCode = newValue;
}
},
}, },
components: { components: {
textBlock, textBlock,

View file

@ -1,6 +1,8 @@
import { applicationConfig } from "@/constants/application-config";
const externalUrls = { const externalUrls = {
HERITAGE_FUNNEL: process.env.VUE_APP_HERITAGE_FUNNEL, HERITAGE_FUNNEL: applicationConfig.HERITAGE_FUNNEL,
SAFELITE_HOP: process.env.VUE_APP_SAFELITE_HOP, SAFELITE_HOP: applicationConfig.SAFELITE_HOP,
}; };
export { externalUrls }; export { externalUrls };

View file

@ -1,20 +1,5 @@
process.env.VUE_APP_CONSUMER_CF_DISTRO = "https://digitalapi.dev.safelite.io"; const webpack = require('webpack');
//process.env.VUE_APP_HERITAGE_FUNNEL = "http://localhost:38000/default.aspx"; const { environmentVariablesWithDefaults } = require('./environment-variables.js');
process.env.VUE_APP_HERITAGE_FUNNEL = "https://fixmyglassdev.safelite.com/default.aspx";
process.env.VUE_APP_GOOGLE_PLACES_API_KEY = "AIzaSyDptGCkOPgN2uWJOy4ou4M33phRD4MAoJo";
process.env.VUE_APP_CURRENT_ENVIRONMENT = "Localhost";
process.env.VUE_APP_MY_ACCOUNT = "https://myaccountdev.safelite.com/";
//process.env.VUE_APP_SAFELITE_HOP = "http://localhost:60966/fmgCheckoutShared.aspx";
process.env.VUE_APP_SAFELITE_HOP = "https://sv2-safelitehop-dev.safelite.com/fmgCheckoutShared.aspx";
process.env.VUE_APP_SOLARWINDS_MONITORING = "";
process.env.VUE_APP_SESSION_TIMEOUT_MINUTES = 30;
// GA & GTM
process.env.VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY =
"(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl+ '&gtm_auth=amlAYNhxUxuskQo7jmjadg&gtm_preview=env-38&gtm_cookies_win=x';f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-M6XCRH');";
process.env.VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC =
"https://www.googletagmanager.com/ns.html?id=GTM-M6XCRH&gtm_auth=amlAYNhxUxuskQo7jmjadg&gtm_preview=env-38&gtm_cookies_win=x";
module.exports = { module.exports = {
outputDir: "dist/fmg", outputDir: "dist/fmg",
@ -36,5 +21,8 @@ module.exports = {
}, },
configureWebpack: { configureWebpack: {
devtool: "source-map", devtool: "source-map",
plugins: [
new webpack.EnvironmentPlugin(environmentVariablesWithDefaults),
],
}, },
}; };

View file

@ -1,16 +1,5 @@
process.env.VUE_APP_CONSUMER_CF_DISTRO = "__VUE_APP_CONSUMER_CF_DISTRO__"; const webpack = require('webpack');
process.env.VUE_APP_GOOGLE_PLACES_API_KEY = "__VUE_APP_GOOGLE_PLACES_API_KEY__"; const { environmentVariablesWithoutDefaults } = require('./environment-variables.js');
process.env.VUE_APP_HERITAGE_FUNNEL = "__VUE_APP_HERITAGE_FUNNEL__";
process.env.VUE_APP_CURRENT_ENVIRONMENT = "__VUE_APP_CURRENT_ENVIRONMENT__";
process.env.VUE_APP_MY_ACCOUNT = "__VUE_APP_MY_ACCOUNT__";
process.env.VUE_APP_SAFELITE_HOP = "__VUE_APP_SAFELITE_HOP__";
process.env.VUE_APP_SOLARWINDS_MONITORING_SCRIPT = "__VUE_APP_SOLARWINDS_MONITORING_SCRIPT__";
process.env.VUE_APP_SESSION_TIMEOUT_MINUTES = 30;
// GA & GTM
process.env.VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY = "__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__";
process.env.VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC =
"__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__";
module.exports = { module.exports = {
outputDir: "dist/fmg", outputDir: "dist/fmg",
@ -30,4 +19,9 @@ module.exports = {
}, },
}, },
}, },
configureWebpack: {
plugins: [
new webpack.EnvironmentPlugin(environmentVariablesWithoutDefaults),
],
},
}; };