Merge branch 'develop' into feature/CASH-1123

This commit is contained in:
kpatel8hs4io 2025-09-26 10:26:48 -04:00 committed by GitHub
commit 12baa76a49
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 634 additions and 539 deletions

View file

@ -110,18 +110,6 @@ stages:
clearFolder: true
deployFolder: ""
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
parameters:
awsCliContainer: awscli
@ -171,18 +159,6 @@ stages:
clearFolder: true
deployFolder: ""
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
parameters:
awsCliContainer: awscli
@ -232,18 +208,6 @@ stages:
clearFolder: true
deployFolder: ""
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
parameters:
awsCliContainer: awscli
@ -293,18 +257,6 @@ stages:
clearFolder: true
deployFolder: ""
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
parameters:
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 = {
verbose: true,
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
//silent: true,
};
process.env = Object.assign(process.env, environmentVariablesWithDefaults);

View file

@ -4,7 +4,7 @@
<script>
window.dataLayer = [{}];
</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>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
@ -16,7 +16,7 @@
<!-- Google Tag Manager -->
<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>
</noscript>
@ -27,6 +27,6 @@
<div id="app"></div>
<!-- built files will be auto injected -->
<%= process.env.VUE_APP_SOLARWINDS_MONITORING_SCRIPT %>
<%= process.env.__VUE_APP_SOLARWINDS_MONITORING_SCRIPT__ %>
</body>
</html>

View file

@ -1,12 +1,12 @@
const applicationConfig = {
CONSUMER_CF_DISTRO: process.env.VUE_APP_CONSUMER_CF_DISTRO,
GOOGLE_PLACES_API_KEY: process.env.VUE_APP_GOOGLE_PLACES_API_KEY,
CONSUMER_CF_DISTRO: process.env.__VUE_APP_CONSUMER_CF_DISTRO__,
GOOGLE_PLACES_API_KEY: process.env.__VUE_APP_GOOGLE_PLACES_API_KEY__,
ANALYTICS_SESSION_TIMEOUT_MINUTES: 30,
SAVED_SESSION_TIMEOUT_DAYS: 45,
COOKIE_PATH: "/",
CURRENT_ENVIRONMENT: process.env.VUE_APP_CURRENT_ENVIRONMENT, // "Localhost", "Dev", "QA", and "Prod"
HERITAGE_FUNNEL: process.env.VUE_APP_HERITAGE_FUNNEL,
SAFELITE_HOP: process.env.VUE_APP_SAFELITE_HOP,
CURRENT_ENVIRONMENT: process.env.__VUE_APP_CURRENT_ENVIRONMENT__, // "Localhost", "Dev", "QA", and "Prod"
HERITAGE_FUNNEL: process.env.__VUE_APP_HERITAGE_FUNNEL__,
SAFELITE_HOP: process.env.__VUE_APP_SAFELITE_HOP__,
APPLICATION_NAME: "FixMyGlass",
ANALYTICS_APPLICATION_NAME: "FixMyGlassNextGen",
APPLICATION_ABBREVIATION: "fmg",
@ -14,7 +14,7 @@ const applicationConfig = {
SITE_ENTRY_TRIGGER_VALUE: "FixMyGlass",
CASH_PARENT_ACCOUNT_NUMBER: 167132,
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:
location.protocol + "//" + location.host + "/fmg/payment-pia-return?src=concept-funnel",
PIA_CANCEL_URL:
@ -29,7 +29,7 @@ const applicationConfig = {
FRONTEND_LOGGER_PATH: "/analytics/api/v1/logging",
AFFILIATE_COOKIE_CONTAINING_NAME: "_Track",
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",
};

View file

@ -2,16 +2,6 @@
<div
class="date-picker text-center"
: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">
<legend class="sr-only">Select a day and time</legend>
<div
@ -139,36 +129,11 @@
@click="showAnotherMonth">
View more dates
</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>
</div>
</template>
<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
import loader from "@/ux-components/loader/loader";
import store from "@/store";
@ -178,12 +143,6 @@ import {
MONTHS_OF_YEAR,
DAYS_OF_WEEK,
} from "@/digital-components/date-picker/mixins/constants";
import {
AppointmentTypeStrings,
RouteCodeFlags,
PREMIUM_FEE_PART_TYPE,
cmsWidgetFieldMappings,
} from "@/constants/schedule-constants";
import {
selectableDaysOptions,
requiredParameter,
@ -195,7 +154,6 @@ import {
import { useField, ErrorMessage } from "vee-validate";
import { deepClone } from "@/helpers/object-helper";
import { v4 as uuidv4 } from "uuid";
import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helper";
export default {
name: "datePicker",
@ -205,10 +163,8 @@ export default {
months: null,
disableViewMoreDatesButton: false,
hideSomeDaysForInitialView: null,
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
selectableDatesInshop: [], // NOTE: this and the mobile version below use monthNum (1-based), NOT monthIndex (0-based)
selectableDatesMobile: [],
durationTextBlockCopyForInshopOrDropoff: null,
};
},
props: {
@ -242,12 +198,6 @@ export default {
pricingByDayBasePrice: Number,
pricingByDayUpcharge: Number,
isPricingByDayExperiment: Boolean,
timeSlotsForSelectedDate: Object,
appointmentType: String,
premiumAppointmentFee: Object,
estimatedServiceMinutesMinimum: Number,
estimatedServiceMinutesMaximum: Number,
displayWaitList: Boolean,
isMobileSelected: Boolean,
},
setup(props) {
@ -309,80 +259,10 @@ export default {
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: {
async initializeComponent(initialData) {
await this.setCalendarData(initialData);
this.$refs.timeSlotModalQuestion.initializeComponent();
},
fireDateSelectedEvent(event, date) {
// Ignore if arrow key selected radioButton
@ -949,42 +829,9 @@ export default {
};
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() {
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: {
modelValue(newValue) {
@ -998,19 +845,10 @@ export default {
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: {
loader,
ErrorMessage,
timeSlotQuestion,
textBlock,
funnelSubHeader,
},
};
</script>
@ -1028,14 +866,6 @@ export default {
align-items: flex-start;
text-align: left;
.duration-text-block {
text-align: left;
}
.date-picker-header {
margin-bottom: 1.5rem;
}
fieldset {
flex-grow: 1;
position: relative;

View file

@ -43,7 +43,7 @@
]"
:validationRules="validationRules"
@change="handleChange"
@blur="handleChange"
@blur="handleBlur"
:maxlength="maxLength ? maxLength : '999'"
@focus="$emit('focus', $event.target.value)"
@keydown="keyDownHandler"

View file

@ -195,6 +195,7 @@ export default {
name: "cart",
props: {
modelValue: Object,
inactivePromos: Object,
damage: Object,
servicePackageOptionsCmsName: String,
availableVaps: Object,
@ -327,15 +328,26 @@ export default {
this.lineItems.vaps = [...lineItems.vaps];
this.lineItems.promos = [...lineItems.promos];
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(
storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
{
activePromos: this.lineItems.promos,
inactivePromos: this.inactivePromos,
},
false
);
},
updateInactivePromos(newInactivePromos) {
this.$emit("update:inactivePromos", newInactivePromos);
},
getPromoCodeList() {
if (this.$refs["promoModalQuestion"]) {
return this.$refs["promoModalQuestion"].getPromoCodeList();

View file

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

View file

@ -275,6 +275,7 @@ export default {
}
.sub-text strong {
text-transform: uppercase;
font-weight: 400;
}
}

View file

@ -6,6 +6,15 @@ import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import store from "@/store";
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.
jest.mock("@/helpers/layout-helper.js", () => ({
@ -34,7 +43,6 @@ describe("payment-method.vue", () => {
// Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled();
});
test("if the continue button is clicked, navigate forward", async () => {
// Arrange
const { wrapper } = setupMocks();
@ -81,7 +89,10 @@ function setupMocks() {
city: "Anytown",
state: "OH",
zipCode: "00000",
provider: { providerNumber: "0000567" },
},
vehicle: { year: 2020, make: "Toyota", model: "Camry", carId: "12345" },
damage: { isRepair: false },
},
policy: {
currentDeductible: 123,
@ -101,6 +112,9 @@ function setupMocks() {
store: {
getters: store.getters,
},
actions: {
priceOrderItemsAndSaveServerData: mockPriceOrderItemsAndSaveServerData,
},
});
const mockMixin = {

View file

@ -22,6 +22,7 @@
:availableVaps="availableVaps"
:allowItemRemoval="true"
v-model="lineItems"
v-model:inactivePromos="inactivePromos"
pageName="payment-method"
servicePackageOptionsCmsName="ServicePackageTitle"
recyclingModalCmsWidgetName="RecycleModal"
@ -263,7 +264,7 @@ export default {
: (resultMap.wipers.filter((wiper) => wiper.partType == partTypeStrings.REAR_WIPER) ??
[]);
const lineItemsToTax = [
const allLineItems = [
resultMap.rainDefense,
...supportingItems,
...availableFrontWipers,
@ -272,6 +273,10 @@ export default {
...vaps,
];
const lineItemsToTax = Array.from(
new Map(allLineItems.map((item) => [item.partNumber, item])).values()
);
const availableVaps = [
resultMap.rainDefense,
...availableFrontWipers,
@ -542,6 +547,16 @@ export default {
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();
},
getIsRecalAckOptInFromStore() {
@ -563,6 +578,7 @@ export default {
this.paymentMethod,
false
);
// save lineitems as they now have salestax added
await this.dispatchStoreAction(
storeActions.SAVE_GLASS_PARTS_SUPPRESSING_STATE_RESETTING,
@ -820,7 +836,7 @@ export default {
isAfterpayBreakoutDisplay() {
return (
this.showInsuranceCoverageAs !== coverageStatus.PENDING &&
(!this.isRecalPriceRemove || !this.isRecalibrationOnOrder) &&
!this.isRecalPriceRemove &&
this.hasSettingEqualTo(experimentSettings.DISPLAY_AFTERPAY_BREAKOUT_DISPLAY, "true")
);
},

View file

@ -2,7 +2,9 @@
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
<loadingModal ref="loadingModal" />
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
<div class="container quote position-relative">
<div
class="container quote position-relative"
:class="[showSaveProgressPopup ? 'initial-popup-open' : '']">
<div class="row justify-content-center">
<div class="col-12">
<funnelSubHeader class="mb-5" cmsWidgetName="FunnelSubHeaderWidget" />
@ -286,6 +288,7 @@ export default {
const isEmailInStoreOnPageLoad = emailFromStore?.length > 0;
const isPopupSkipped =
store.getters.pageData(routeData.QUOTE.name)?.saveProgressPopupSkipped === true;
// "popup" is the automatic opening centered popup on load
const showSaveProgressPopup = !(isEmailInStoreOnPageLoad || isPopupSkipped);
const showSaveProgressModal = isEmailInStoreOnPageLoad ? false : true;
const shouldSkipToInsurance =
@ -555,8 +558,8 @@ export default {
servicePackage: null,
holdParentAccountNumber: null,
holdBillToAccountNumber: null,
showSaveProgressPopup: null,
showSaveProgressModal: null,
showSaveProgressPopup: null, // "popup" is the automatic opening centered popup on load
showSaveProgressModal: null, // "modal" is the user-triggered slide-in drawer
isSaveProgressComplete: null,
packageNumber: null,
skipToInsurance: null,
@ -899,6 +902,10 @@ export default {
<style lang="scss" scoped>
// Unique to quote page for 2.0/Heritage parity
// prettier-ignore
.prevent-modal-scroll.modal-open .container.quote.initial-popup-open .row:not(.save-progress-popup) {
display: none;
}
.quote {
padding-bottom: 2rem;

View file

@ -246,7 +246,7 @@ export default {
},
isAfterpayBreakoutDisplay() {
return (
(!this.shouldHideRecalibration || !this.isRecalibrationOnOrder) &&
!this.shouldHideRecalibration &&
experimentMixin.methods.hasSettingEqualTo(
experimentSettings.DISPLAY_AFTERPAY_BREAKOUT_DISPLAY,
"true"

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="row">
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
<alert
ref="alertMobileFeeFree"
class="mb-5"
cmsWidgetName="AlertMobileFeeFreeWidget"
v-if="showMobileFreeAlert"
alertClass="alert-success" />
<mobileFeeWaiverAlert
cmsWidgetName="MobileFeeFreeDropdownWidget"
v-if="showMobileFreeAlert" />
</div>
</div>
<div class="row appointment-type">
<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
v-model="appointmentTypeFromAppointmentTypeQuestion"
v-show="isAppointmentTypeDisplayed"
@ -118,6 +117,15 @@
<div class="row date-picker-wrapper">
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
<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
:currentZip="zipCode"
:currentProviderNumber="selectedProvider?.providerNumber"
@ -134,14 +142,25 @@
:pricingByDayBasePrice="pricingByDayBasePrice"
:pricingByDayUpcharge="pricingByDayUpcharge"
:showPricingByDay="showPricingByDay"
:isPricingByDayExperiment="isPricingByDayExperiment"
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
:isPricingByDayExperiment="isPricingByDayExperiment" />
<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"
:premiumAppointmentFee="mobilePremiumAppointmentFee"
:estimatedServiceMinutesMinimum="getServiceMinutesMin"
:estimatedServiceMinutesMaximum="getServiceMinutesMax"
@timeSlotSelected="updateTimeSlot"
:displayWaitList="displayWaitList"
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
:isSameDay="isSameDay"
:selectedRouteCodeData="selectedRouteCodeData"
@waitListRequested="handleWaitListRequested" />
<navbar
cmsWidgetName="FunnelFooterWidget"
@ -160,9 +179,14 @@
import alert from "@/ux-components/alert/alert";
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 shopQuestion from "@/layouts/service-location/shop-question/shop-question";
import shopListButton from "@/layouts/service-location/shop-question/shop-list-button/shop-list-button";
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 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 contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal";
import textBlock from "@/digital-components/text-block/text-block";
import mobileFeeWaiverAlert from "./mobile-fee-waiver-alert/mobile-fee-waiver-alert.vue";
// Supporting files
import baseMixin from "@/mixins/base-mixin.js";
@ -497,14 +522,14 @@ export default {
);
const serviceZipCode = store.getters.order.serviceLocation.zipCode;
const zipCodeDataPromise = getZipCodeData(serviceZipCode);
const zipCodeDataPromise = getZipCodeData(serviceZipCode, to.name);
const serviceabilityDetailsPromise = getServiceabilityDetails(
serviceZipCode,
null,
"service-location"
to.name
);
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode, "service-location");
const shopProviderData = await getShopProviderData(serviceZipCode);
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode, to.name);
const shopProviderData = await getShopProviderData(serviceZipCode, to.name);
const providerNumber = shopProviderData?.data?.shopProviders[0]?.providerNumber;
// Get pricingByDayUpcharge needed for Pricing By Day
@ -515,7 +540,7 @@ export default {
const premiumFeePromise = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_MOBILE_PREMIUM_FEE,
null,
"schedule"
to.name
);
const premiumFeeWithPricePromise = premiumFeePromise.then((result) => {
if (result.data) {
@ -524,7 +549,7 @@ export default {
{
availableLineItems: [result.data],
},
"schedule",
to.name,
false
);
} else {
@ -626,6 +651,16 @@ export default {
return this.isGlassServiceableMobile;
}
},
showTimeSlotQuestion() {
if (
this.selectedDate &&
this.selectedDate.includes("mobile") &&
!this.isMobileSelected
) {
this.getNewSelectedDate();
}
return this.selectedDate && this.appointmentTypeFromAppointmentTypeQuestion;
},
isMobileStaticRecalibrationApplicable() {
return (
this.displayMSR &&
@ -785,6 +820,21 @@ export default {
}
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() {
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
return this.splitCopyOnCMSPlaceHolder(this.ChangeShopLinkText);
},
getServiceMinutesMin() {
estimatedServiceMinutesMinimum() {
return this.isMobileSelected
? this.selectableDatesMobile.estimatedServiceMinutesMinimum
: this.selectableDatesInshop.estimatedServiceMinutesMinimum;
},
getServiceMinutesMax() {
estimatedServiceMinutesMaximum() {
return this.isMobileSelected
? this.selectableDatesMobile.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: {
splitCopyOnCMSPlaceHolder,
@ -1305,7 +1367,7 @@ export default {
) {
navigateToHeritageFunnel({
shouldSaveSession: false,
pageNameToLog: "service-location",
pageNameToLog: this.pageName,
navType: "back",
});
} else {
@ -1558,22 +1620,46 @@ export default {
}
},
updateTimeSlot(timeSlotObj) {
if (this.preSelectedDate && !timeSlotObj?.timeSlot?.date) return;
this.selectedTimeSlotInfo = timeSlotObj;
if (!timeSlotObj?.routeCode) {
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 (
this.appointmentType !== AppointmentTypeStrings.MOBILE &&
this.appointmentType !== null
) {
// update apptType based on routeCode to determine if it should be dropoff or inshop
debugLog(`Updating appointment type based on route code.`);
debugLog(`Route code =`, timeSlotObj.timeSlot.routeCode);
debugLog(
`Parsed type = `,
this.getInShopOrDropOffApptType(timeSlotObj.timeSlot.routeCode)
);
this.appointmentType = this.getInShopOrDropOffApptType(
timeSlotObj.timeSlot.routeCode
);
debugLog(`Route code =`, timeSlotObj.routeCode);
debugLog(`Parsed type = `, this.getInShopOrDropOffApptType(timeSlotObj.routeCode));
this.appointmentType = this.getInShopOrDropOffApptType(timeSlotObj.routeCode);
}
},
getInShopOrDropOffApptType(routeCode) {
@ -1607,8 +1693,13 @@ export default {
},
updateSelectedProvider(newProvider) {
if (newProvider) {
const didProviderNumberChange =
this.selectedProvider.providerNumber !== newProvider.providerNumber;
this.selectedProvider = newProvider;
this.initializeDatePicker();
if (didProviderNumberChange) {
this.initializeDatePicker();
}
} else if (this.appointmentType === this.appointmentTypeStrings.MOBILE) {
this.selectedProvider = {
providerNumber: this.shopProviderData.mobileProviderNumber.toString(),
@ -1630,7 +1721,7 @@ export default {
handleZipCodeChange(newZipCode) {
this.resetMobileLocation();
getShopProviderData(newZipCode.zipCode).then((result) => {
getShopProviderData(newZipCode.zipCode, this.pageName).then((result) => {
this.shopProviderData = result.data;
this.resetSelectedProvider();
this.state = newZipCode.state;
@ -1697,6 +1788,9 @@ export default {
this.selectedDate = this.getSelectedDate();
this.setDisplayWaitList();
},
getNewSelectedDate() {
this.selectedDate = this.getSelectedDate();
},
},
watch: {
appointmentTypeFromAppointmentTypeQuestion: {
@ -1740,12 +1834,15 @@ export default {
datePicker,
locationAlerts,
textBlock,
timeSlotQuestion,
alert,
serviceZipModalQuestion,
appointmentTypeQuestion,
contentGroupModal,
durationTextBlock,
shopLocation,
mobileFeeWaiverAlert,
},
};
</script>
@ -1780,12 +1877,17 @@ export default {
font-weight: 600;
}
:deep(.funnel-sub-header:not(.mb-5)) {
:deep(.funnel-sub-header:not(#schedule-your-service)) {
h5.dark-header {
font-weight: 600;
font-size: 1rem;
}
}
:deep(.funnel-sub-header#schedule-your-service) {
h5.dark-header {
margin-bottom: 0;
}
}
.col-12 a {
font-family: UrbanistSemibold;

View file

@ -3,6 +3,7 @@
<buttonQuestion
v-if="isDropOffAppointmentAvailable"
v-model="selectedAnswerForDropOffOrInshop"
@update:modelValue="dropOffSelectionChanged"
:questionText="dropOffOrPickATimeQuestionLabelText"
:questionTextDescription="dropOffOrPickATimeQuestionDescription"
:answers="answersForDropOffQuestion"
@ -24,6 +25,7 @@
<buttonQuestion
ref="buttonQuestion"
v-if="shouldDisplayTimeSlotQuestion"
@update:modelValue="timeSlotSelectionChanged"
buttonTypeString="timeSlotModalListButton"
:buttonTypeObject="timeSlotModalListButton"
class="mt-4 timeslots"
@ -44,7 +46,7 @@
v-if="supplementalInformationBlock"
v-html="supplementalInformationBlock" />
<div
v-if="hasWaitListExperiment && displayWaitListFeature && selectedDate"
v-if="hasWaitListExperiment && displayWaitListFeature"
class="mt-5 bg-light rounded waitlist">
<textBlock
cmsWidgetName="WaitListLabelWidget"
@ -59,7 +61,7 @@
@click="waitListChecked" />
</div>
<div
v-if="waitListRequested && displayWaitList && selectedDate"
v-if="waitListRequested && displayWaitList"
class="rounded waitlist-success"
ref="waitlistSuccessMessage">
<img :src="waitListSuccessImage" class="success-image" />
@ -92,7 +94,6 @@ import {
isDropOffRouteCode,
militaryToTwelveHourTime,
} from "@/layouts/schedule/helpers/schedule-helper";
import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helper";
// Validation
import { defineRule, useField } from "vee-validate";
@ -116,23 +117,8 @@ defineRule("time-slot-required", required(errorMessages.OPTION_REQUIRED));
export default {
name: "time-slot-question",
emits: ["update:modelValue", "TimeSlotSelected", "click-event"],
emits: ["update:modelValue", "time-slot-selection-changed"],
props: {
modelValue: {
type: Object,
default: () => ({
timeSlot: {
routeCode: null,
date: null,
startTime: null,
endTime: null,
jobMaxMinutes: null,
jobMinMinutes: null,
},
isPremiumAppointment: null,
}),
},
cmsWidgetName: String,
mobileCmsWidgetName: String,
mobilePremiumCmsWidgetName: String,
dropoffCmsWidgetName: String,
@ -142,21 +128,23 @@ export default {
appointmentType: String,
timeSlotsForSelectedDate: Object,
premiumAppointmentFee: Object,
estimatedServiceMinutesMinimum: Number,
estimatedServiceMinutesMaximum: Number,
validationRules: String,
customComponentId: String,
selectedDate: String,
isSameDay: Boolean,
displayWaitList: Boolean,
selectedRouteCodeData: Object,
},
data() {
return {
selectedRouteCode: null,
timeSlotModalListButton: timeSlotModalListButton,
selectedAnswerForDropOffOrInshop: null,
selectedAnswerForTimeSlots: null,
selectedAnswerForDropOffOrInshop: null,
};
},
mounted() {
this.updateDropoffAndTimeSlotAnswersFromSelectedRouteCodeData(this.selectedRouteCodeData);
this.autoSelectTimeSlotIfOnlyOneIsAvailable();
},
setup(props) {
const uuid = uuidv4();
const componentId = !props.customComponentId
@ -186,20 +174,33 @@ export default {
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: {
supplementalInformationBlock() {
let appointmentTypeCmsWidgetName;
if (
this.selectedDate == null ||
this.appointmentType === AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF ||
this.appointmentType === AppointmentTypeStrings.IN_SHOP ||
this.appointmentType === AppointmentTypeStrings.DROP_OFF ||
!this.availableTimeSlots
) {
if (!this.selectedAnswerForTimeSlots && !this.selectedAnswerForDropOffOrInshop) {
this.autoSelectTimeSlotIfOnlyOneIsAvailable();
}
return null;
// 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
@ -214,11 +215,7 @@ export default {
// );
// }
} else {
if (!this.selectedAnswerForTimeSlots && this.selectedRouteCode) {
// Clearing selectedRouteCode if no time slot is selected
this.resetSelectedTimeSlot();
}
appointmentTypeCmsWidgetName = this.selectedRouteCode?.includes(
appointmentTypeCmsWidgetName = this.selectedRouteCodeData?.routeCode?.includes(
PREMIUM_TIME_SLOT_ID_FLAG
)
? this.mobilePremiumCmsWidgetName
@ -299,13 +296,6 @@ export default {
// return null;
// }
// },
isSameDay() {
if (!this.timeSlotsForSelectedDate) {
return false;
}
const todaysDate = new Date().toISOString().split("T")[0];
return this.selectedDate === todaysDate;
},
availableTimeSlots() {
if (!this.timeSlotsForSelectedDate) {
return null;
@ -407,29 +397,60 @@ export default {
},
},
methods: {
initializeComponent() {
this.setSelectedRouteCodeFromParent();
},
async setSelectedTimeSlot() {
this.selectedRouteCode =
this.selectedAnswerForTimeSlots || this.selectedAnswerForDropOffOrInshop;
this.$emit(
"update:modelValue",
this.getSelectedTimeSlotInfoObject(this.selectedRouteCode)
);
},
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;
updateDropoffAndTimeSlotAnswersFromSelectedRouteCodeData(selectedRouteCodeData) {
if (selectedRouteCodeData?.routeCode) {
let routeCode = selectedRouteCodeData.routeCode;
if (isDropOffRouteCode(routeCode)) {
this.selectedAnswerForDropOffOrInshop = routeCode;
} else {
this.selectedAnswerForDropOffOrInshop = PICK_A_TIME_BUTTON_VALUE;
if (this.selectedRouteCodeData.isPremiumAppointment) {
routeCode = this.addPremiumFlagToInput(routeCode);
}
this.selectedAnswerForTimeSlots = routeCode;
}
} else {
this.selectedAnswerForDropOffOrInshop = null;
this.selectedAnswerForTimeSlots = null;
}
},
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) {
return timeSlotsForSelectedDate
.filter((timeSlot) => {
@ -463,8 +484,6 @@ export default {
);
}
this.resetSelectedTimeSlotIfDateChange(timeSlotsForSelectedDate);
return availableTimeSlots;
},
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() {
return store.getters.order.customer?.waitListRequested;
},
autoSelectTimeSlotIfOnlyOneIsAvailable() {
const numberOfOptions = this.timeSlotsForSelectedDate?.timeSlots?.length;
if (this.appointmentType && numberOfOptions === 1) {
if (numberOfOptions === 1) {
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;
} else {
this.selectedAnswerForDropOffOrInshop = this.answersForDropOffQuestion[0].value;
}
this.setSelectedTimeSlot();
}
},
addPremiumFlagToInput(routeCode) {
@ -517,41 +520,6 @@ export default {
removePremiumFlagFromInput(routeCode) {
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) {
this.$emit("waitListRequested", event.target.checked);
},
@ -561,65 +529,6 @@ export default {
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: {
textBlock,

View file

@ -7,8 +7,8 @@ export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) {
return null;
}
const zipCodeData = await getZipCodeData(serviceZipCode);
const providerData = await getShopProviderData(serviceZipCode);
const zipCodeData = await getZipCodeData(serviceZipCode, pageNameToLog);
const providerData = await getShopProviderData(serviceZipCode, pageNameToLog);
// Get the Mobile Fee Part
const mobileFeePart = await baseMixin.methods.dispatchStoreActionWithLogging(
@ -50,7 +50,7 @@ export async function getPricedRecycleFeePart(serviceZipCode, pageNameToLog) {
return null;
}
const zipCodeData = await getZipCodeData(serviceZipCode);
const zipCodeData = await getZipCodeData(serviceZipCode, pageNameToLog);
// Get supporting items
const supportingItems = await baseMixin.methods.dispatchStoreActionWithLogging(
@ -102,23 +102,23 @@ export async function getServiceabilityDetails(serviceZipCode, lineItems, pageNa
);
}
export async function getBillToAccountNumber(providerNumber) {
export async function getBillToAccountNumber(providerNumber, pageNameToLog) {
// Get the BillTo Account Number
return await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_BILL_TO_ACCOUNT_NUMBER,
{ providerNumber: providerNumber },
"service-location",
pageNameToLog,
false
);
}
export async function getShopProviderData(serviceZipCode) {
export async function getShopProviderData(serviceZipCode, pageNameToLog) {
return await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_PROVIDERS,
{
serviceZipCode: serviceZipCode,
},
"service-location"
pageNameToLog
);
}
@ -139,6 +139,6 @@ export async function getClosestApplicableShops(serviceZipCode, carId, pageNameT
return closestShops.data;
}
export async function getZipCodeData(serviceZipCode) {
return await baseMixin.methods.getZipCodeData(serviceZipCode, "service-location");
export async function getZipCodeData(serviceZipCode, pageNameToLog) {
return await baseMixin.methods.getZipCodeData(serviceZipCode, pageNameToLog);
}

View file

@ -193,8 +193,7 @@ export default {
return;
}
const zipCodeData = await this.getZipCodeData(
this.internalModel.addressQuestions.zipCode,
"service-location"
this.internalModel.addressQuestions.zipCode
);
if (!zipCodeData.isValid) {
@ -216,26 +215,21 @@ export default {
// retrieve mobile fee part
const serviceZipCode = this.internalModel.addressQuestions.zipCode;
const mobileFeePart = await getPricedMobileFeePart(
serviceZipCode,
"service-location"
);
const mobileFeePart = await getPricedMobileFeePart(serviceZipCode, this.pageName);
// retrieve recycle fee part
const recycleFeePart = await getPricedRecycleFeePart(
serviceZipCode,
"service-location"
);
const recycleFeePart = await getPricedRecycleFeePart(serviceZipCode, this.pageName);
// retrieve serviceability details
const serviceabilityDetails = await getServiceabilityDetails(
serviceZipCode,
null,
"service-location"
this.pageName
);
const billToAccountNumber = await getBillToAccountNumber(
this.internalModel.zipCodeCtu
this.internalModel.zipCodeCtu,
this.pageName
);
// update content related to service zip code

View file

@ -226,17 +226,17 @@ export default {
const cmsContentPromise = fetchCmsContentForPage(to.name);
const serviceZipCode = store.getters.order.serviceLocation.zipCode;
const zipCodeDataPromise = getZipCodeData(serviceZipCode);
const zipCodeDataPromise = getZipCodeData(serviceZipCode, to.name);
const serviceabilityDetailsPromise = getServiceabilityDetails(
serviceZipCode,
null,
"service-location"
to.name
);
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode, "service-location");
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode, to.name);
const shopProviderDataPromise = getShopProviderData(serviceZipCode); // shopQuestion.methods.loadInitialData(serviceZipCode);
const shopProviderDataPromise = getShopProviderData(serviceZipCode, to.name); // shopQuestion.methods.loadInitialData(serviceZipCode);
// Settle promises and get results
const promiseResultMap = [
@ -846,7 +846,7 @@ export default {
zipCode: {
handler(newValue) {
if (!this.navigatingForward) {
getShopProviderData(this.zipCode).then(async (result) => {
getShopProviderData(this.zipCode, this.pageName).then(async (result) => {
this.shopProviderData = result.data;
if (this.selectedAppointmentType === "Mobile") {
this.selectedProvider = new Provider(

View file

@ -162,7 +162,7 @@ export default {
const zipCodeData = await this.getZipCodeData(
this.internalModel.zipCode,
"service-location"
this.pageName
);
if (!zipCodeData.isValid) {
@ -175,7 +175,7 @@ export default {
const closestShops = await getClosestApplicableShops(
this.internalModel.zipCode,
this.carId,
"service-location"
this.pageName
);
if (!closestShops || closestShops.inShopProviders?.length === 0) {
@ -193,24 +193,25 @@ export default {
const serviceZipCode = this.internalModel.zipCode;
const mobileFeePart = await getPricedMobileFeePart(
serviceZipCode,
"service-location"
this.pageName
);
// retrieve recycle fee part
const recycleFeePart = await getPricedRecycleFeePart(
serviceZipCode,
"service-location"
this.pageName
);
// retrieve serviceability details
const serviceabilityDetails = await getServiceabilityDetails(
serviceZipCode,
null,
"service-location"
this.pageName
);
const billToAccountNumber = await getBillToAccountNumber(
this.internalModel.zipCodeCtu
this.internalModel.zipCodeCtu,
this.pageName
);
// update content related to service zip code

View file

@ -280,7 +280,7 @@ export default {
}
this.isLoading = true;
this.zipCodeData = await this.getZipCodeData(this.localZipCode, "service-location");
this.zipCodeData = await this.getZipCodeData(this.localZipCode, this.pageName);
// Add zipCode to zipCodeData as it does not come back from endpoint
this.zipCodeData.zipCode = this.localZipCode;
if (this.zipCodeData.isValid) {
@ -289,7 +289,7 @@ export default {
const closestShops = await getClosestApplicableShops(
this.localZipCode,
this.$store.getters.order.vehicle.carId,
"service-location"
this.pageName
);
if (!closestShops || closestShops.inShopProviders?.length === 0) {
this.isLoading = false;

View file

@ -318,10 +318,6 @@ export default {
"vin-lookup"
);
// Update button "Continue with..."
this.$refs.navbar.updateButtonText(
`Continue with ${resultMap.vehicleLookupResponse.year} ${resultMap.vehicleLookupResponse.make} ${resultMap.vehicleLookupResponse.model}`
);
return this.$refs.navbar.removeLoader();
}

View file

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

View file

@ -1,26 +1,21 @@
<template>
<loadingModal v-if="useLoadingModal" notFullScreen ref="loadingModal" />
<a v-if="linkType === 'navigation'" @click="handleClick" class="navigation-link" :href="href">
{{ text }}
<slot name="after-text"></slot>
</a>
<!-- NOTE: each prettier-ignore is needed below...
each anchor that follows needs to be one single line in markup or adds unwanted spaces within link (fixes CASH-1502) -->
<!-- prettier-ignore -->
<a v-if="linkType === 'navigation'" @click="handleClick" class="navigation-link" :href="href">{{ text }}<slot name="after-text"></slot></a>
<!-- prettier-ignore -->
<a
v-else-if="linkType === 'newWindowLink'"
@click="handleClick"
class="new-window-link"
:href="href"
target="_blank">
{{ text }}
<slot name="after-text"></slot>
</a>
<a v-else-if="linkType === 'textSmall'" @click="handleClick" class="small" :href="href">
{{ text }}
<slot name="after-text"></slot>
</a>
<a v-else-if="linkType === 'text'" @click="handleClick" :href="href">
{{ text }}
<slot name="after-text"></slot>
</a>
target="_blank">{{ text }}<slot name="after-text"></slot></a>
<!-- prettier-ignore -->
<a v-else-if="linkType === 'textSmall'" @click="handleClick" class="small" :href="href">{{ text }}<slot name="after-text"></slot></a>
<!-- prettier-ignore -->
<a v-else-if="linkType === 'text'" @click="handleClick" :href="href">{{ text }}<slot name="after-text"></slot></a>
</template>
<script>

View file

@ -1,20 +1,5 @@
process.env.VUE_APP_CONSUMER_CF_DISTRO = "https://digitalapi.dev.safelite.io";
//process.env.VUE_APP_HERITAGE_FUNNEL = "http://localhost:38000/default.aspx";
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";
const webpack = require('webpack');
const { environmentVariablesWithDefaults } = require('./environment-variables.js');
module.exports = {
outputDir: "dist/fmg",
@ -36,5 +21,8 @@ module.exports = {
},
configureWebpack: {
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__";
process.env.VUE_APP_GOOGLE_PLACES_API_KEY = "__VUE_APP_GOOGLE_PLACES_API_KEY__";
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__";
const webpack = require('webpack');
const { environmentVariablesWithoutDefaults } = require('./environment-variables.js');
module.exports = {
outputDir: "dist/fmg",
@ -30,4 +19,9 @@ module.exports = {
},
},
},
configureWebpack: {
plugins: [
new webpack.EnvironmentPlugin(environmentVariablesWithoutDefaults),
],
},
};