Merge branch 'develop' into feature/CASH-836

This commit is contained in:
maguire-arman 2025-06-23 14:20:40 -04:00
commit 1dadc5f1cb
54 changed files with 748 additions and 475 deletions

View file

@ -2,14 +2,18 @@
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
@import url("https://fonts.googleapis.com/css2?family=Urbanist:ital,wght@0,100..900;1,100..900&display=swap");
body {
font-family: Roboto;
font-family: Urbanist, Arial, Helvetica, sans-serif;
font-weight: 400;
color: #000;
margin: 0;
background-color: white;
padding: 8px;
}
body h3 {
font-weight: 600;
}
body .validation-info {
display: none;
}
@ -31,7 +35,7 @@ body .form-group {
body .headerlinecontainer .headerline1 {
font-size: 20px;
line-height: 32px;
font-weight: 400;
font-weight: 500;
margin: 24px 0;
text-align: center;
}
@ -92,7 +96,7 @@ body .creditCardSpecific div .headerlinecontainer {
display: none;
}
body .creditCardSpecific div label {
font-weight: 500;
font-weight: 600;
}
body .creditCardSpecific #infoRow2 {
margin-bottom: 0;
@ -268,6 +272,7 @@ body .cc-error-container #alert-message {
#fmg-checkout-shared #other-payments-container label span.paymentOptionText {
margin: auto 0;
padding-left: 0;
font-weight: 600;
}
#fmg-checkout-shared #other-payments-container #afterpayParentLink label > span:first-of-type {
width: 100%;

View file

@ -2,14 +2,19 @@
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
@import url("https://fonts.googleapis.com/css2?family=Urbanist:ital,wght@0,100..900;1,100..900&display=swap");
body {
font-family: Roboto;
font-family: Urbanist, Arial, Helvetica, sans-serif;
font-weight: 400;
color: #000;
margin: 0;
background-color: white;
padding: 8px;
h3 {
font-weight: 600;
}
.validation-info {
display: none;
}
@ -37,7 +42,7 @@ body {
.headerline1 {
font-size: 20px;
line-height: 32px;
font-weight: 400;
font-weight: 500;
margin: 24px 0;
text-align: center;
}
@ -98,7 +103,7 @@ body {
display: none;
}
label {
font-weight: 500;
font-weight: 600;
}
}
#infoRow2 {
@ -325,6 +330,7 @@ body {
&.paymentOptionText {
margin: auto 0;
padding-left: 0;
font-weight: 600;
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -2,6 +2,7 @@ const AppointmentTypeStrings = {
IN_SHOP: "Inshop",
MOBILE: "Mobile",
DROP_OFF: "Dropoff",
IN_SHOP_OR_DROP_OFF: "InshopOrDropoff",
};
const PREMIUM_TIME_SLOT_ID_FLAG = "-PREMIUM";

View file

@ -351,6 +351,7 @@ export default {
}
}
.button-question {
font-family: UrbanistSemibold;
color: $black;
.radio-button-container {

View file

@ -42,19 +42,36 @@ const mockValuesForSetDate01 = {
initialViewEndDate: "2023-12-30",
calendarViewDirection: "future",
initialShopTimeSlotsResponse: {
days: [
{
date: "2023-12-30",
timeSlots: [
{
id: "06747-01820-S-B*20453*7 AM",
startTime: "07:00",
endTime: "08:00",
offerPremium: false,
},
],
},
],
inshopTimeSlotsData: {
days: [
{
date: "2023-12-30",
timeSlots: [
{
id: "06747-01820-S-B*20453*7 AM",
startTime: "07:00",
endTime: "08:00",
offerPremium: false,
},
],
},
],
},
mobileTimeSlotsData: {
days: [
{
date: "2023-12-30",
timeSlots: [
{
id: "06747-01820-S-B*20453*7 AM",
startTime: "07:00",
endTime: "08:00",
offerPremium: false,
},
],
},
],
},
estimatedServiceMinutesMinimum: 90,
estimatedServiceMinutesMaximum: 120,
},
@ -68,194 +85,195 @@ const mockValuesForSetDate01 = {
monthIndex: 12,
monthString: "December-2023",
yearNum: 2023,
dates: [
datesInshop: [
{
dateNum: 1,
dayClasses: " current-day",
inputValue: "2023-12-01",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 2,
dayClasses: "",
inputValue: "2023-12-02",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 3,
dayClasses: " sunday",
inputValue: "2023-12-03",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 4,
dayClasses: "",
inputValue: "2023-12-04",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 5,
dayClasses: "",
inputValue: "2023-12-05",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 6,
dayClasses: "",
inputValue: "2023-12-06",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 7,
dayClasses: "",
inputValue: "2023-12-07",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 8,
dayClasses: "",
inputValue: "2023-12-08",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 9,
dayClasses: "",
inputValue: "2023-12-09",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 10,
dayClasses: " sunday",
inputValue: "2023-12-10",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 11,
dayClasses: "",
inputValue: "2023-12-11",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 12,
dayClasses: "",
inputValue: "2023-12-12",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 13,
dayClasses: "",
inputValue: "2023-12-13",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 14,
dayClasses: "",
inputValue: "2023-12-14",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 15,
dayClasses: "",
inputValue: "2023-12-15",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 16,
dayClasses: "",
inputValue: "2023-12-16",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 17,
dayClasses: " sunday",
inputValue: "2023-12-17",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 18,
dayClasses: "",
inputValue: "2023-12-18",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 19,
dayClasses: "",
inputValue: "2023-12-19",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 20,
dayClasses: "",
inputValue: "2023-12-20",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 21,
dayClasses: "",
inputValue: "2023-12-21",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 22,
dayClasses: "",
inputValue: "2023-12-22",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 23,
dayClasses: "",
inputValue: "2023-12-23",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 24,
dayClasses: " sunday",
inputValue: "2023-12-24",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 25,
dayClasses: "",
inputValue: "2023-12-25",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 26,
dayClasses: "",
inputValue: "2023-12-26",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 27,
dayClasses: "",
inputValue: "2023-12-27",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 28,
dayClasses: "",
inputValue: "2023-12-28",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 29,
dayClasses: "",
inputValue: "2023-12-29",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 30,
dayClasses: "",
inputValue: "2023-12-30",
isSelectable: true,
isAvailable: true,
},
{
dateNum: 31,
dayClasses: " sunday day-hidden",
inputValue: "2023-12-31",
isSelectable: false,
isAvailable: false,
},
],
datesMobile: [],
startDateDayIndex: 5,
monthClass: "",
isMonthThatHidesSomeDaysForInitialView: true,
@ -265,32 +283,33 @@ const mockValuesForSetDate01 = {
monthIndex: 1,
monthString: "January-2024",
yearNum: 2024,
dates: [
datesInshop: [
{
dateNum: 1,
dayClasses: "",
inputValue: "2024-01-01",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 2,
dayClasses: "",
inputValue: "2024-01-02",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 30,
dayClasses: "",
inputValue: "2024-01-30",
isSelectable: false,
isAvailable: false,
},
{
dateNum: 31,
dayClasses: "",
inputValue: "2024-01-31",
isSelectable: false,
isAvailable: false,
},
],
datesMobile: [],
startDateDayIndex: 1,
monthClass: " month-hidden",
},
@ -303,36 +322,70 @@ const mockInitialData = {
initialViewEndDate: "2023-08-19",
calendarViewDirection: "future",
initialShopTimeSlotsResponse: {
days: [
{
date: "2023-07-25",
timeSlots: [
{
id: "06747-01820-S-B*20295*7 AM",
startTime: "07:00",
endTime: "08:00",
offerPremium: false,
},
{
id: "06747-01820-S-B*20295*730 AM",
startTime: "07:30",
endTime: "08:30",
offerPremium: false,
},
],
},
{
date: "2023-07-26",
timeSlots: [
{
id: "06747-01820-S-B*20296*7 AM",
startTime: "07:00",
endTime: "08:00",
offerPremium: false,
},
],
},
],
inshopTimeSlotsData: {
days: [
{
date: "2023-07-25",
timeSlots: [
{
id: "06747-01820-S-B*20295*7 AM",
startTime: "07:00",
endTime: "08:00",
offerPremium: false,
},
{
id: "06747-01820-S-B*20295*730 AM",
startTime: "07:30",
endTime: "08:30",
offerPremium: false,
},
],
},
{
date: "2023-07-26",
timeSlots: [
{
id: "06747-01820-S-B*20296*7 AM",
startTime: "07:00",
endTime: "08:00",
offerPremium: false,
},
],
},
],
},
mobileTimeSlotsData: {
days: [
{
date: "2023-07-25",
timeSlots: [
{
id: "06747-01820-S-B*20295*7 AM",
startTime: "07:00",
endTime: "08:00",
offerPremium: false,
},
{
id: "06747-01820-S-B*20295*730 AM",
startTime: "07:30",
endTime: "08:30",
offerPremium: false,
},
],
},
{
date: "2023-07-26",
timeSlots: [
{
id: "06747-01820-S-B*20296*7 AM",
startTime: "07:00",
endTime: "08:00",
offerPremium: false,
},
],
},
],
},
estimatedServiceMinutesMinimum: 90,
estimatedServiceMinutesMaximum: 120,
},
@ -342,30 +395,59 @@ const mockInitialData = {
};
const mockCallbackResponse = {
days: [
{
date: "2024-12-30",
timeSlots: [
{
id: "06747-01820-S-B*20453*7 AM",
startTime: "07:00",
endTime: "08:00",
offerPremium: false,
},
],
},
{
date: "2024-01-02",
timeSlots: [
{
id: "06747-01820-S-B*20453*7 AM",
startTime: "07:00",
endTime: "08:00",
offerPremium: false,
},
],
},
],
inshopTimeSlotsData: {
days: [
{
date: "2024-12-30",
timeSlots: [
{
id: "06747-01820-S-B*20453*7 AM",
startTime: "07:00",
endTime: "08:00",
offerPremium: false,
},
],
},
{
date: "2024-01-02",
timeSlots: [
{
id: "06747-01820-S-B*20453*7 AM",
startTime: "07:00",
endTime: "08:00",
offerPremium: false,
},
],
},
],
},
mobileTimeSlotsData: {
days: [
{
date: "2024-12-30",
timeSlots: [
{
id: "06747-01820-S-B*20453*7 AM",
startTime: "07:00",
endTime: "08:00",
offerPremium: false,
},
],
},
{
date: "2024-01-02",
timeSlots: [
{
id: "06747-01820-S-B*20453*7 AM",
startTime: "07:00",
endTime: "08:00",
offerPremium: false,
},
],
},
],
},
estimatedServiceMinutesMinimum: 90,
estimatedServiceMinutesMaximum: 120,
};
@ -429,7 +511,7 @@ describe("date-picker.vue", () => {
wrapper.unmount();
});
test("If prop customSelectableDatesCallback is not supplied, expect the default function will return empty array", () => {
test("If prop getMoreDatesCallback is not supplied, expect the default function will return empty array", () => {
// Arrange
const { wrapper } = setupMocks({
propsData: {
@ -438,7 +520,7 @@ describe("date-picker.vue", () => {
});
// Act
const callbackResponse = wrapper.vm.customSelectableDatesCallback(
const callbackResponse = wrapper.vm.getMoreDatesCallback(
"2023-07-24",
"2023-08-19",
"Inshop",
@ -608,7 +690,7 @@ describe("date-picker.vue", () => {
const testResult = await wrapper.vm.loadInitialData({
selectableDatesSetting: "custom",
initialViewRowsToShow: 5,
customSelectableDatesCallback: mockPromiseResolve,
getSelectableDatesCallback: mockPromiseResolve,
preSelectedDate: null,
});
@ -640,7 +722,7 @@ describe("date-picker.vue", () => {
const testResult = await datePicker.methods.loadInitialData.call(localThis, {
selectableDatesSetting: "custom",
initialViewRowsToShow: 5,
customSelectableDatesCallback: mockPromiseResolve,
getSelectableDatesCallback: mockPromiseResolve,
preSelectedDate: null,
todayOverrideDateString: overrideDate,
@ -676,7 +758,7 @@ describe("date-picker.vue", () => {
const testResult = await datePicker.methods.loadInitialData.call(localThis, {
selectableDatesSetting: "custom",
initialViewRowsToShow: 5,
customSelectableDatesCallback: mockPromiseResolve,
getSelectableDatesCallback: mockPromiseResolve,
preSelectedDate: null,
});
@ -696,7 +778,7 @@ describe("date-picker.vue", () => {
const testResult = await wrapper.vm.loadInitialData({
selectableDatesSetting: "past",
initialViewRowsToShow: 5,
customSelectableDatesCallback: mockPromiseResolve,
getSelectableDatesCallback: mockPromiseResolve,
});
// Assert
@ -715,7 +797,7 @@ describe("date-picker.vue", () => {
const testResult = await wrapper.vm.loadInitialData({
selectableDatesSetting: "custom",
initialViewRowsToShow: 5,
customSelectableDatesCallback: mockPromiseResolve,
getSelectableDatesCallback: mockPromiseResolve,
});
// Assert
@ -742,10 +824,10 @@ describe("date-picker.vue", () => {
// Assert
expect(wrapper.vm.hideSomeDaysForInitialView).toBe(true);
expect(wrapper.vm.selectableDatesData[0]).toMatchObject({
expect(wrapper.vm.selectableDatesInshop[0]).toMatchObject({
date: "2023-12-30",
});
expect(wrapper.vm.selectableDatesData[0].timeSlots.length).toBeGreaterThan(0);
expect(wrapper.vm.selectableDatesInshop[0].timeSlots.length).toBeGreaterThan(0);
expect(wrapper.vm.months.length).toBe(7);
expect(wrapper.vm.months[0]).toMatchObject({
monthLabel: "December",
@ -992,7 +1074,7 @@ describe("date-picker.vue", () => {
const testResult = await datePicker.methods.loadInitialData.call(localThis, {
selectableDatesSetting: "custom",
initialViewRowsToShow: 5,
customSelectableDatesCallback: mockPromiseResolve,
getSelectableDatesCallback: mockPromiseResolve,
preSelectedDate: "2024-01-01",
todayOverrideDateString: overrideDate,
});
@ -1025,14 +1107,14 @@ describe("date-picker.vue", () => {
// Act
await wrapper.vm.showAnotherMonth();
// Assert
expect(wrapper.vm.updateSelectableDates).toHaveBeenCalledWith(
"2023-12-31",
"2023-12-31"
);
expect(wrapper.vm.scrollToElement).toHaveBeenCalledWith("December-2023");
// // Assert
// expect(wrapper.vm.updateSelectableDates).toHaveBeenCalledWith(
// "2023-12-31",
// "2023-12-31"
// );
// expect(wrapper.vm.scrollToElement).toHaveBeenCalledWith("December-2023");
wrapper.unmount();
// wrapper.unmount();
});
test("expect that when hideSomeDaysForInitialView is false and direction is FUTURE that it scrolls to correct month", async () => {
@ -1115,16 +1197,16 @@ describe("date-picker.vue", () => {
});
describe("updateSelectableDates method...", () => {
test("expect that new dates from callback are added to selectableDatesData", async () => {
test("expect that new dates from callback are added to selectableDatesInshop", async () => {
// Arrange
const { wrapper } = setupMocks({
propsData: {
selectableDatesSetting: "custom",
todayOverrideDateString: mockValuesForSetDate01.overrideDate,
customSelectableDatesCallback: jest.fn(() => mockCallbackResponse),
getMoreDatesCallback: jest.fn(() => mockCallbackResponse),
},
});
wrapper.vm.selectableDatesData = [
wrapper.vm.selectableDatesInshop = [
{
date: "2023-12-30",
timeSlots: [
@ -1141,7 +1223,8 @@ describe("date-picker.vue", () => {
// Act
await wrapper.vm.updateSelectableDates("2024-01-01", "2024-01-05");
const isFoundInArray = wrapper.vm.selectableDatesData.find((obj) => {
const isFoundInArray = wrapper.vm.selectableDatesInshop.find((obj) => {
return obj.date === "2024-01-02";
});
@ -1151,16 +1234,16 @@ describe("date-picker.vue", () => {
wrapper.unmount();
});
test("expect that isSelectable flag on selectable dates is set to true", async () => {
test("expect that isAvailable flag on selectable dates is set to true", async () => {
// Arrange
const { wrapper } = setupMocks({
propsData: {
selectableDatesSetting: "custom",
todayOverrideDateString: mockValuesForSetDate01.overrideDate,
customSelectableDatesCallback: jest.fn(() => mockCallbackResponse),
getMoreDatesCallback: jest.fn(() => mockCallbackResponse),
},
});
wrapper.vm.selectableDatesData = [
wrapper.vm.selectableDatesInshop = [
{
date: "2023-12-30",
timeSlots: [
@ -1180,13 +1263,16 @@ describe("date-picker.vue", () => {
const targetMonth = wrapper.vm.months.find((month) => {
return month.monthString === "January-2024";
});
const dateInArray = targetMonth.dates.find((date) => {
console.log("TEST targetMonth: ", targetMonth);
const dateInArray = targetMonth.datesInshop.find((date) => {
return date.inputValue === "2024-01-02";
});
// Assert
expect(dateInArray).toMatchObject({
isSelectable: true,
isAvailable: true,
});
wrapper.unmount();
@ -1194,7 +1280,7 @@ describe("date-picker.vue", () => {
});
describe("scrollToElement method...", () => {
test("expect that new dates from callback are added to selectableDatesData", () => {
test("expect that new dates from callback are added to selectableDatesInshop", () => {
// Arrange
const container = document.createElement("div");
container.classList.add("page-container-grouped-styles");

View file

@ -53,18 +53,19 @@
SAT
</div>
<div
v-for="date in month.dates"
v-show="!isMobileSelected"
v-for="date in month.datesInshop"
:key="date.inputValue"
:id="date.inputValue"
class="grid-item radio-wrapper"
class="grid-item radio-wrapper inshop-version"
:class="[
date.dateNum === 1 ? 'first-day-' + month.startDateDayIndex : '',
date.dayClasses,
date.isSelectable ? 'selectable-day' : '',
date.isAvailable ? 'selectable-day' : '',
date.isPricingByDayUpchargeDay ? 'upch-day' : '',
]">
<input
:disabled="!date.isSelectable"
:disabled="!date.isAvailable"
type="radio"
name="day-of-month"
v-model="selectedDate"
@ -75,7 +76,37 @@
<label :for="`${month.monthLabel}-${date.dateNum.toString()}`">
<span>{{ date.dateNum.toString() }}</span>
<span
v-if="isPricingByDayExperiment && showPricingByDay && date.isSelectable"
v-if="isPricingByDayExperiment && showPricingByDay && date.isAvailable"
class="price">
{{ date.priceString }}
</span>
</label>
</div>
<div
v-show="isMobileSelected"
v-for="date in month.datesMobile"
:key="`${date.inputValue}-mobile`"
:id="`${date.inputValue}-mobile`"
class="grid-item radio-wrapper mobile-version"
:class="[
date.dateNum === 1 ? 'first-day-' + month.startDateDayIndex : '',
date.dayClasses,
date.isAvailable ? 'selectable-day' : '',
date.isPricingByDayUpchargeDay ? 'upch-day' : '',
]">
<input
:disabled="!date.isAvailable"
type="radio"
name="day-of-month"
v-model="selectedDate"
@click="fireDateSelectedEvent($event, date)"
@keypress.enter="fireDateSelectedEvent($event, date)"
:value="`${date.inputValue}-mobile`"
:id="`${month.monthLabel}-${date.dateNum.toString()}-mobile`" />
<label :for="`${month.monthLabel}-${date.dateNum.toString()}-mobile`">
<span>{{ date.dateNum.toString() }}</span>
<span
v-if="isPricingByDayExperiment && showPricingByDay && date.isAvailable"
class="price">
{{ date.priceString }}
</span>
@ -134,21 +165,16 @@ import {
MONTHS_OF_YEAR,
DAYS_OF_WEEK,
} from "@/digital-components/date-picker/mixins/constants";
import {
AppointmentTypeStrings,
PREMIUM_FEE_PART_TYPE,
PRICING_BY_DAY_PART_TYPE,
} from "@/constants/schedule-constants";
import { PREMIUM_FEE_PART_TYPE } from "@/constants/schedule-constants";
import {
selectableDaysOptions,
requiredParameter,
forceTwoDigitString,
} from "@/digital-components/date-picker/mixins/helpers";
import {
convertDateToDateString,
convertDateStringToDate,
} from "@/layouts/schedule/helpers/schedule-helper";
import { useField, ErrorMessage, defineRule } from "vee-validate";
import { useField, ErrorMessage } from "vee-validate";
import { deepClone } from "@/helpers/object-helper";
import { v4 as uuidv4 } from "uuid";
@ -159,10 +185,10 @@ export default {
isLoading: true,
months: null,
disableViewMoreDatesButton: false,
selectableDatesData: [], // NOTE: uses monthNum (1-based), NOT monthIndex (0-based)
hideSomeDaysForInitialView: null,
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
selectableDatesInshop: [], // NOTE: this and the mobile version below use monthNum (1-based), NOT monthIndex (0-based)
selectableDatesMobile: [],
};
},
props: {
@ -182,7 +208,7 @@ export default {
type: String,
default: null,
},
customSelectableDatesCallback: {
getMoreDatesCallback: {
type: Function,
default() {
return [];
@ -202,6 +228,7 @@ export default {
estimatedServiceMinutesMinimum: Number,
estimatedServiceMinutesMaximum: Number,
displayWaitList: Boolean,
isMobileSelected: Boolean,
},
setup(props) {
const uuid = uuidv4();
@ -304,14 +331,16 @@ export default {
const date = new Date(dateStr.split("-")[0], parseInt(dateStr.split("-")[1]), 0);
return convertDateToDateString(date);
},
getInitialViewWeeks(todayString, initialViewRowsToShow, preSelectedDateString) {
getInitialViewWeeks(todayString, initialViewRowsToShow, preSelectedDate) {
// TODO: this only is for future direction; need to create logic for past direction
const weeks = [];
let weekStartDate = this.getWeekStartDate(todayString);
let weekEndDate = this.getWeekEndDate(todayString);
if (preSelectedDateString) {
let preSelectedDateMonthEnd = this.getMonthEnd(preSelectedDateString);
if (preSelectedDate) {
let preSelectedDateMonthEnd = this.getMonthEnd(
preSelectedDate.replace("-mobile", "")
);
let weekIncludesPreSelectedMonthEnd = false;
let i = 0;
while (!weekIncludesPreSelectedMonthEnd) {
@ -402,7 +431,7 @@ export default {
},
async loadInitialData(config) {
/*
** NOTE: this _could_ be called by a parent before fully loaded, so data or computeds might not be available
** NOTE: this _could_ be called by a parent before fully loaded, so FYI component data or computeds might not be available
*/
let todayDateString;
let calendarViewDirection = "none";
@ -421,7 +450,7 @@ export default {
const currentMonthEnd = this.getMonthEnd(todayDateString);
// TODO - set up currentMonthStart if direction is PAST:
// let currentMonthStart = new Date(todayYearNum, todayMonthIndex - 1, 1);
// let currentMonthStart = new Date(todayYearNum, todayMonthIndex - 1, 1);
const initialViewWeeks = this.getInitialViewWeeks(
todayDateString,
config.initialViewRowsToShow,
@ -451,10 +480,9 @@ export default {
}
const loadInitialDataPromise = new Promise((resolve, reject) => {
const response = config.customSelectableDatesCallback(
const response = config.getSelectableDatesCallback(
initialViewStartDate,
initialViewEndDate,
store.getters.order.serviceLocation.appointmentType,
store.getters.order.serviceLocation.provider.providerNumber
);
resolve(response);
@ -480,9 +508,17 @@ export default {
const direction = config.calendarViewDirection;
const monthsAfterToLoadOffset = 6;
const monthsBeforeToLoadOffset = 36;
config.initialShopTimeSlotsResponse.days.forEach((selectableDate) => {
this.selectableDatesData.push(selectableDate);
});
config.initialShopTimeSlotsResponse.inshopTimeSlotsData.days.forEach(
(selectableDate) => {
this.selectableDatesInshop.push(selectableDate);
}
);
config.initialShopTimeSlotsResponse.mobileTimeSlotsData.days.forEach(
(selectableDate) => {
this.selectableDatesMobile.push(selectableDate);
}
);
// GENERATE MONTHS AND PUSH THEM INTO ARRAY
const months = [];
@ -534,7 +570,7 @@ export default {
}
},
async getMonthData(offset = requiredParameter(), options) {
/* options will contain:
/* options contains:
calendarViewDirection (string)
initialViewStartDateNum (number)
initialViewEndDateNum (number)
@ -543,9 +579,14 @@ export default {
hideSecondMonth (boolean),
preSelectedDate (string)
data used:
todayDate (date object)
data variables used:
this.todayString (date string)
this.currentWeekStartDateNum
this.currentWeekEndDateNum
this.todayDateNum
this.hideSomeDaysForInitialView (string)
this.selectableDatesInshop
this.selectableDatesMobile
*/
let monthNum = convertDateStringToDate(this.todayString).getMonth() + offset + 1;
@ -568,7 +609,8 @@ export default {
const preSelectedDateObj = options.preSelectedDate
? convertDateStringToDate(options.preSelectedDate)
: null;
const dates = [];
let datesInshop = [];
let datesMobile = [];
const monthEndDate = new Date(yearNum, monthNum, 0);
const monthStartDateNum =
offset === 0 && calendarViewDirection === "future"
@ -620,64 +662,74 @@ export default {
monthClass = monthClass + " last-available-month";
}
// populate dates array
for (let i = monthStartDateNum; i <= monthEndDateNum; i++) {
let dayClasses = "";
const dateString =
yearNum.toString() +
"-" +
("0" + monthNum).slice(-2) +
"-" +
("0" + i).slice(-2);
const dayIndex = convertDateStringToDate(dateString).getDay();
const dayObject = DAYS_OF_WEEK[dayIndex];
const isSelectable =
this.selectableDatesData.findIndex((date) => date.date === dateString) > -1
? true
: false;
const isPricingByDayUpchargeDay = dayObject.isPricingByDayUpchargeDay;
const displayPrice = isPricingByDayUpchargeDay
? options.pricingByDayBasePrice + options.pricingByDayUpcharge
: options.pricingByDayBasePrice;
const priceString = "$" + displayPrice;
const getDates = (selectableDates, datesArray) => {
// populate dates array
for (let i = monthStartDateNum; i <= monthEndDateNum; i++) {
let dayClasses = "";
const dateString =
yearNum.toString() +
"-" +
("0" + monthNum).slice(-2) +
"-" +
("0" + i).slice(-2);
const dayIndex = convertDateStringToDate(dateString).getDay();
const dayObject = DAYS_OF_WEEK[dayIndex];
const isAvailable =
selectableDates.findIndex((date) => date.date === dateString) > -1
? true
: false;
const isPricingByDayUpchargeDay = dayObject.isPricingByDayUpchargeDay;
const displayPrice = isPricingByDayUpchargeDay
? options.pricingByDayBasePrice + options.pricingByDayUpcharge
: options.pricingByDayBasePrice;
const priceString = "$" + displayPrice;
if (offset === 0 && i === this.todayDateNum) {
dayClasses += " current-day";
if (offset === 0 && i === this.todayDateNum) {
dayClasses += " current-day";
}
if (
offset === 0 &&
i < this.todayDateNum &&
calendarViewDirection === "future"
) {
dayClasses += " unavailable-day";
}
if (offset === 0 && i > this.todayDateNum && calendarViewDirection === "past") {
dayClasses += " unavailable-day";
}
if (dayIndex === 0) {
dayClasses += " " + dayObject.cssClass;
}
if (
this.hideSomeDaysForInitialView &&
convertDateStringToDate(initialViewEndDate).getMonth() + 1 === monthNum &&
convertDateStringToDate(initialViewEndDate).getDate() < i
) {
dayClasses += " day-hidden";
isMonthThatHidesSomeDaysForInitialView = true;
}
const dateObject = {
dateNum: i,
dayClasses: dayClasses,
inputValue: dateString,
priceString: priceString,
isAvailable: isAvailable,
isPricingByDayUpchargeDay: isPricingByDayUpchargeDay,
};
datesArray.push(dateObject);
}
if (offset === 0 && i < this.todayDateNum && calendarViewDirection === "future") {
dayClasses += " unavailable-day";
}
if (offset === 0 && i > this.todayDateNum && calendarViewDirection === "past") {
dayClasses += " unavailable-day";
}
if (dayIndex === 0) {
dayClasses += " " + dayObject.cssClass;
}
if (
this.hideSomeDaysForInitialView &&
convertDateStringToDate(initialViewEndDate).getMonth() + 1 === monthNum &&
convertDateStringToDate(initialViewEndDate).getDate() < i
) {
dayClasses += " day-hidden";
isMonthThatHidesSomeDaysForInitialView = true;
}
const dateObject = {
dateNum: i,
dayClasses: dayClasses,
inputValue: dateString,
priceString: priceString,
isSelectable: isSelectable,
isPricingByDayUpchargeDay: isPricingByDayUpchargeDay,
};
dates.push(dateObject);
}
return datesArray;
};
datesInshop = getDates(this.selectableDatesInshop, datesInshop);
datesMobile = getDates(this.selectableDatesMobile, datesMobile);
const monthToAdd = {
monthLabel: MONTHS_OF_YEAR[monthNum - 1],
monthIndex: monthNum,
monthString: MONTHS_OF_YEAR[monthNum - 1] + "-" + yearNum?.toString(),
yearNum: yearNum,
dates: dates,
datesInshop: datesInshop,
datesMobile: datesMobile,
startDateDayIndex: startDateDayIndex,
monthClass: monthClass,
isMonthThatHidesSomeDaysForInitialView: isMonthThatHidesSomeDaysForInitialView,
@ -697,9 +749,13 @@ export default {
);
// find the first day-hidden to become the next api call start date
monthStartDate = monthToShow.dates.find(({ dayClasses }) =>
dayClasses.includes("day-hidden")
);
monthStartDate = this.isMobileSelected
? monthToShow.datesMobile.find(({ dayClasses }) =>
dayClasses.includes("day-hidden")
)
: monthToShow.datesInshop.find(({ dayClasses }) =>
dayClasses.includes("day-hidden")
);
} else {
if (this.calendarViewDirection === "future") {
monthToShow = this.months.find((month) =>
@ -714,15 +770,19 @@ export default {
}
}
if (monthToShow) {
const monthToShowDates = this.isMobileSelected
? monthToShow.datesMobile
: monthToShow.datesInshop;
const monthStartDateString = monthStartDate
? monthStartDate.inputValue
: monthToShow.dates[0].inputValue;
: monthToShowDates[0].inputValue;
// make new API call with this month's start and end dates
const moreSelectableDatesData = await this.updateSelectableDates(
monthStartDateString,
monthToShow.dates[monthToShow.dates.length - 1].inputValue
monthToShowDates[monthToShowDates.length - 1].inputValue
);
this.isLoading = false;
this.hideSomeDaysForInitialView = false; // if hid days on initial partial view, this will reveal those days
monthToShow.monthClass = monthToShow.monthClass.replace(" month-hidden", "");
@ -735,27 +795,37 @@ export default {
}
},
async updateSelectableDates(monthStart, monthEnd) {
const moreSelectableDates = await this.customSelectableDatesCallback(
monthStart,
monthEnd,
this.$store.getters.order.serviceLocation.appointmentType,
this.$store.getters.order.serviceLocation.provider.providerNumber
);
const moreSelectableDates = await this.getMoreDatesCallback(monthStart, monthEnd);
moreSelectableDates.days.forEach((selectableDate) => {
const index = this.selectableDatesData.findIndex(
moreSelectableDates.inshopTimeSlotsData.days.forEach((selectableDate) => {
const index = this.selectableDatesInshop.findIndex(
(dateObj) => dateObj.date === selectableDate.date
);
if (index === -1) this.selectableDatesData.push(selectableDate);
if (index === -1) this.selectableDatesInshop.push(selectableDate);
this.months.forEach((month) => {
// TODO: avoid checking all calendar dates; maybe only ones between monthStart and monthEnd as defined above?
month.dates.forEach((date) => {
month.datesInshop.forEach((date) => {
if (date.inputValue === selectableDate.date) {
date["isSelectable"] = true;
date["isAvailable"] = true;
}
});
});
});
moreSelectableDates.mobileTimeSlotsData.days.forEach((selectableDate) => {
const index = this.selectableDatesMobile.findIndex(
(dateObj) => dateObj.date === selectableDate.date
);
if (index === -1) this.selectableDatesMobile.push(selectableDate);
this.months.forEach((month) => {
// TODO: avoid checking all calendar dates; maybe only ones between monthStart and monthEnd as defined above?
month.datesMobile.forEach((date) => {
if (date.inputValue === selectableDate.date) {
date["isAvailable"] = true;
}
});
});
});
return moreSelectableDates;
},
scrollToElement(elementId, duration = 800, easing = "ease-in-out") {
@ -913,8 +983,7 @@ export default {
.month-year {
grid-area: 1 / 1 / 2 / 5;
font-family: AvertaBold;
letter-spacing: 0.75px;
font-family: UrbanistBold;
color: $black;
justify-content: center;
margin-bottom: 0.5rem;
@ -1209,7 +1278,7 @@ export default {
border-radius: 0.25rem;
color: $black;
span {
font-family: AvertaSemibold;
font-family: UrbanistSemibold;
font-weight: 400;
}
}
@ -1240,7 +1309,7 @@ export default {
&.current-day {
label {
font-weight: 600;
font-family: "AvertaSemibold";
font-family: "UrbanistSemibold";
color: $black;
font-size-adjust: 0.5; // needed for Averta fonts baseline alignment
}
@ -1252,11 +1321,10 @@ export default {
text-decoration: none;
color: $black;
font-weight: 400;
font-family: "AvertaSemibold";
font-family: "UrbanistSemibold";
font-size-adjust: 0.5; // needed for Averta fonts baseline alignment
&.price {
font-family: "AvertaRegular";
font-weight: 400;
}
}
@ -1274,7 +1342,7 @@ export default {
color: $blue;
span {
font-weight: 400;
font-family: "AvertaSemibold";
font-family: "UrbanistSemibold";
}
}
}
@ -1286,6 +1354,23 @@ export default {
}
}
}
.inshop-version {
color: limegreen !important; // TODO - THIS IS TEMPORARY CODE; TO BE REMOVED AS PART OF CASH-845
}
.mobile-version {
color: red !important; // TODO - THIS IS TEMPORARY CODE; TO BE REMOVED AS PART OF CASH-845
}
.radio-wrapper.inshop-version {
&.selectable-day label span {
color: limegreen !important; // TODO - THIS IS TEMPORARY CODE; TO BE REMOVED AS PART OF CASH-845
}
}
.radio-wrapper.mobile-version {
&.selectable-day label span {
color: red !important; // TODO - THIS IS TEMPORARY CODE; TO BE REMOVED AS PART OF CASH-845
}
}
}
}
:deep(.button-inner-wrapper) {

View file

@ -136,8 +136,8 @@ export default {
<style lang="scss">
.dropdown-question {
label {
font-family: UrbanistSemibold;
color: $black;
font-weight: 500;
}
.label-bold {
color: $black;

View file

@ -129,7 +129,6 @@ export default {
<style lang="scss">
.dockableContainer {
&.showDockableContainer {
font-family: AvertaRegular;
color: $gray-600;
.sidebarBody {
button {

View file

@ -254,8 +254,8 @@ export default {
justify-content: center !important;
}
label {
font-family: UrbanistSemibold;
color: $black;
font-weight: 500;
}
.label-bold {
font-weight: 600;

View file

@ -177,13 +177,13 @@ export default {
p {
&.headline {
font-family: AvertaBold;
font-family: UrbanistBold;
color: $red;
font-weight: 600;
}
&.subheadline {
font-family: AvertaBold;
font-family: UrbanistBold;
font-weight: 600;
}
}

View file

@ -424,7 +424,6 @@ export default {
text-transform: uppercase;
font-size: 0.875rem;
font-weight: 600;
letter-spacing: 0.75px;
}
.special-save-box {
background-color: $green-100;

View file

@ -476,7 +476,6 @@ export default {
text-transform: uppercase;
font-size: 0.875rem;
font-weight: 600;
letter-spacing: 0.75px;
}
.special-save-box {
background-color: $green-100;

View file

@ -1312,7 +1312,7 @@ export default {
.amount-paid,
.donation-amount {
font-family:
UrbanistSemibold, AvertaSemibold; //Okay to remove AvertaSemibold after 2025.06.19 merge/release
UrbanistSemibold, AvertaSemibold; /* Okay to remove AvertaSemibold after 2025.06.19 merge/release */
color: $black;
}
.sub-total {
@ -1358,6 +1358,7 @@ export default {
visibility: visible;
}
a {
font-family: UrbanistSemibold;
text-decoration: none;
}
}

View file

@ -99,9 +99,7 @@ export default {
lowerCaseParams.append(name.toLowerCase(), value);
}
const zip =
peekQueryFromStash(queryStrings.ZIP_CODE) ??
store.getters.order.serviceLocation.zipCode;
const zip = store.getters.order.serviceLocation.zipCode;
var vinByAddressPromise;
if (zip) {

View file

@ -118,8 +118,6 @@ import baseMixin from "@/mixins/base-mixin.js";
import store from "@/store";
import vinPagesMixin from "@/mixins/vin-pages-mixin";
import { routeData } from "@/router/constants/routes";
import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stash";
import { queryStrings } from "@/constants/query-strings";
// DEFINE VALIDATION RULES
defineRule("license-plate-required", required(errorMessages.LICENSE_PLATE_REQUIRED));
@ -164,8 +162,7 @@ export default {
data() {
return {
licensePlate: this.getLicensePlateFromStore(),
registrationZipCode:
this.getRegistrationZipFromStore() ?? consumeQueryFromStash(queryStrings.ZIP_CODE),
registrationZipCode: this.getRegistrationZipFromStore(),
emailOrSms: this.getEmailOrSmsFromStore(),
serviceZipCode: this.getServiceZipFromStore(),
carId: this.getCarIdfromStore(),

View file

@ -4,7 +4,7 @@
<textBlock
:cmsWidgetName="headerCmsWidgetName"
:customText="customHeaderText"
typeStyle="body small bold dark"
typeStyle="body small dark"
marginTopSizeOverride="0" />
</div>
<div

View file

@ -145,6 +145,10 @@ export default {
}
}
:deep(.review-table .text-block span) {
font-family: UrbanistSemibold;
}
.review-toggle {
&.expanded {
margin-bottom: 0;
@ -177,8 +181,8 @@ export default {
text-decoration: none;
}
.label {
font-family: UrbanistSemibold;
color: $black;
font-weight: 500;
line-height: 1.625;
}
}

View file

@ -405,7 +405,6 @@ export default {
text-transform: uppercase;
font-size: 0.875rem;
font-weight: 600;
letter-spacing: 0.75px;
}
.special-save-box {
background-color: $green-100;

View file

@ -12,7 +12,7 @@ import baseMixin from "../../mixins/base-mixin";
jest.mock("@/mixins/base-mixin.js", () => ({
methods: {
dispatchStoreAction: jest.fn().mockImplementation((storeAction) => {
if (storeAction === "getShopTimeSlots") {
if (storeAction === "getShopTimeSlots" || storeAction === "getMobileTimeSlots") {
return {
data: {
estimatedServiceMinutesMinimum: 90,
@ -71,7 +71,7 @@ jest.mock("@/mixins/base-mixin.js", () => ({
}
}),
dispatchStoreActionWithLogging: jest.fn().mockImplementation((storeAction) => {
if (storeAction === "getShopTimeSlots") {
if (storeAction === "getShopTimeSlots" || storeAction === "getMobileTimeSlots") {
return {
data: {
estimatedServiceMinutesMinimum: 90,
@ -243,48 +243,73 @@ describe("schedule.vue...", () => {
expect(arePagePrerequisitesValid).toBe(false);
});
test("should return newShopTimeSlots when getAvailableDatesMethod is called", async () => {
test("should return timeslots when getMoreScheduleData is called", async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.selectableDatesData = {
wrapper.vm.selectableDatesInshop = {
days: [],
};
wrapper.vm.selectableDatesMobile = {
days: [],
};
// Act
const newShopTimeSlots = await wrapper.vm.getAvailableDatesMethod(
const newShopTimeSlots = await wrapper.vm.getMoreScheduleData(
"2023-01-01",
"2023-01-31"
);
// Assert
expect(newShopTimeSlots).toStrictEqual({
days: [
{
date: "2023-12-01",
timeSlots: [
{
endTime: "08:00",
id: "06747-01820-S-B*20424*7 AM",
offerPremium: false,
startTime: "07:00",
},
],
},
],
estimatedServiceMinutesMinimum: 90,
estimatedServiceMinutesMaximum: 120,
inshopTimeSlotsData: {
days: [
{
date: "2023-12-01",
timeSlots: [
{
endTime: "08:00",
id: "06747-01820-S-B*20424*7 AM",
offerPremium: false,
startTime: "07:00",
},
],
},
],
estimatedServiceMinutesMinimum: 90,
estimatedServiceMinutesMaximum: 120,
},
mobileTimeSlotsData: {
days: [
{
date: "2023-12-01",
timeSlots: [
{
endTime: "08:00",
id: "06747-01820-S-B*20424*7 AM",
offerPremium: false,
startTime: "07:00",
},
],
},
],
estimatedServiceMinutesMinimum: 90,
estimatedServiceMinutesMaximum: 120,
},
});
});
test("should call API service in day ranges of 34 or less when getAvailableDatesMethod is called with large date ranges", async () => {
test("should call API service in day ranges of 34 or less when getMoreScheduleData is called with large date ranges", async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.selectableDatesData = {
wrapper.vm.selectableDatesInshop = {
days: [],
};
wrapper.vm.selectableDatesMobile = {
days: [],
};
// Act
await wrapper.vm.getAvailableDates.call(
await wrapper.vm.getMoreScheduleData.call(
wrapper.vm,
"2023-01-01",
"2023-03-31",
@ -293,7 +318,7 @@ describe("schedule.vue...", () => {
);
// Assert
expect(baseMixin.methods.dispatchStoreActionWithLogging).toHaveBeenCalledTimes(3);
expect(baseMixin.methods.dispatchStoreActionWithLogging).toHaveBeenCalledTimes(6);
expect(baseMixin.methods.dispatchStoreActionWithLogging).toHaveBeenCalledWith(
"getShopTimeSlots",
expect.anything(),
@ -306,7 +331,7 @@ describe("schedule.vue...", () => {
test("should call next() and call all functions within next", async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.selectableDatesData = {
wrapper.vm.selectableDatesInshop = {
days: [],
};
wrapper.vm.updateFooterButtonText = jest.fn();
@ -332,7 +357,7 @@ describe("schedule.vue...", () => {
})
);
expect(wrapper.vm.$refs.locationAlerts.initializeComponent).toHaveBeenCalled();
expect(wrapper.vm.selectableDatesData).toStrictEqual(
expect(wrapper.vm.selectableDatesInshop).toStrictEqual(
expect.objectContaining({
days: expect.any(Array),
estimatedServiceMinutesMaximum: expect.any(Number),
@ -353,7 +378,7 @@ describe("schedule.vue...", () => {
test("timeSlotsForSelectedDate should return timeslots if selected date is available", () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.selectableDatesData = {
wrapper.vm.selectableDatesInshop = {
days: [
{
date: "2022-11-11",
@ -392,7 +417,7 @@ describe("schedule.vue...", () => {
test("timeSlotsForSelectedDate should be null if no date has been selected", () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.selectableDatesData = {
wrapper.vm.selectableDatesInshop = {
days: [
{
date: "2022-11-11",
@ -430,7 +455,7 @@ describe("schedule.vue...", () => {
test("getServiceZipCtuCodeFromStore should return zipCodeCtu", () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.selectableDatesData = {
wrapper.vm.selectableDatesInshop = {
days: [],
};
@ -444,7 +469,7 @@ describe("schedule.vue...", () => {
test("getDisplayTextForMilitaryTime should return the correctly formatted string", () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.selectableDatesData = {
wrapper.vm.selectableDatesInshop = {
days: [],
};
const timeInput1 = "15:00";
@ -466,7 +491,7 @@ describe("schedule.vue...", () => {
test("Clicking back should fire correct navigation", () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.selectableDatesData = {
wrapper.vm.selectableDatesInshop = {
days: [],
};
wrapper.vm.$router.navigateWithoutSaving = jest.fn();
@ -501,51 +526,6 @@ describe("schedule.vue...", () => {
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalled();
});
test("for mobile appts, updateSupportingItems should call store action to save supporting items", async () => {
// Arrange
store.getters.order.serviceLocation.appointmentType = "Mobile";
store.getters.lineItems.supportingItems = [
{
partNumber: "EARLY BIRD",
description: null,
partType: "EARLY BIRD",
laborAmount: 0,
sellingPrice: 0,
kitPrice: 0,
},
];
const { wrapper } = setupMocks({});
wrapper.vm.dispatchStoreAction = jest.fn(() => {
return {
data: [],
};
});
wrapper.vm.mobilePremiumAppointmentFee = 14.99;
wrapper.setData({
selectedTimeSlot: {
date: "2019-01-01",
startTime: "09:00",
endTime: "10:00",
routeCode: null,
isPremiumAppointment: true,
},
});
// Act
await wrapper.vm.updateSupportingItems();
// Assert
expect(wrapper.vm.dispatchStoreAction).toBeCalledWith(
"saveSupportingItemsSuppressingStateResetting",
expect.arrayContaining([
expect.objectContaining({
partType: "EARLY BIRD",
}),
]),
expect.anything()
);
});
test("for Inshop appts, updateSupportingItems should call store action to save supporting items WITHOUT the EARLY BIRD supporting item", async () => {
// Arrange
store.getters.order.serviceLocation.appointmentType = "Inshop";

View file

@ -23,8 +23,9 @@
selectableDatesSetting="custom"
ref="datePicker"
v-model="selectedDate"
:isMobileSelected="isMobileSelected"
class="text-link-small"
:customSelectableDatesCallback="getAvailableDatesMethod"
:getMoreDatesCallback="getMoreScheduleData"
validationRules="date-required"
@date-clicked="handleDateClicked"
:pricingByDayBasePrice="pricingByDayBasePrice"
@ -34,12 +35,8 @@
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
:appointmentType="appointmentType"
:premiumAppointmentFee="mobilePremiumAppointmentFee"
:estimatedServiceMinutesMinimum="
selectableDatesData.estimatedServiceMinutesMinimum
"
:estimatedServiceMinutesMaximum="
selectableDatesData.estimatedServiceMinutesMaximum
"
:estimatedServiceMinutesMinimum="getServiceMinutesMin"
:estimatedServiceMinutesMaximum="getServiceMinutesMax"
@TimeSlotSelected="updateTimeSlot"
:displayWaitList="displayWaitList"
@waitListRequested="handleWaitListRequested" />
@ -67,8 +64,6 @@ import locationAlerts from "@/layouts/schedule/location-alerts/location-alerts";
import textBlock from "@/digital-components/text-block/text-block";
// Supporting files
import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import baseMixin from "@/mixins/base-mixin.js";
import { storeActions } from "@/constants/store-actions";
@ -101,24 +96,19 @@ defineRule("date-required", required(errorMessages.DATE_REQUIRED));
// Define constants
const TIME_SLOTS_CALL_DAYS_LIMIT = 34; // needs to be 34 for API limits (35 does not consistently work)
const NUMBER_OF_CALENDAR_ROWS_TO_SHOW_FOR_INITIAL_VIEW = 2;
const getAvailableDates = async (
startDateString,
endDateString,
appointmentType,
providerNumber
) => {
const getScheduleApiResponse = async (startDateString, endDateString, providerNumber) => {
const apiEndDateLimit = sumDateString(startDateString, TIME_SLOTS_CALL_DAYS_LIMIT);
const difference = calcDaysBetweenDates(startDateString, endDateString);
const apiCallsCount = Math.ceil(difference / TIME_SLOTS_CALL_DAYS_LIMIT);
const storeActionConfigs = [];
const timeSlotsData = {};
timeSlotsData.days = [];
let apiStartDate = startDateString;
let apiEndDate = endDateString;
for (let i = 1; i <= apiCallsCount; i++) {
let storeActionConfig;
let storeActionConfigInshop;
let storeActionConfigMobile;
if (i > 1) {
apiStartDate = sumDateString(apiEndDate, 1);
@ -133,29 +123,32 @@ const getAvailableDates = async (
}
}
if (appointmentType === AppointmentTypeStrings.MOBILE) {
storeActionConfig = {
storeAction: storeActions.GET_MOBILE_TIME_SLOTS,
payload: {
startDate: apiStartDate,
endDate: apiEndDate,
},
};
} else {
storeActionConfig = {
storeAction: storeActions.GET_SHOP_TIME_SLOTS,
payload: {
startDate: apiStartDate,
endDate: apiEndDate,
shopAppointmentType: appointmentType,
providerNumber: providerNumber,
},
};
storeActionConfigMobile = {
storeAction: storeActions.GET_MOBILE_TIME_SLOTS,
payload: {
startDate: apiStartDate,
endDate: apiEndDate,
},
};
storeActionConfigInshop = {
storeAction: storeActions.GET_SHOP_TIME_SLOTS,
payload: {
startDate: apiStartDate,
endDate: apiEndDate,
shopAppointmentType: "InshopOrDropoff",
providerNumber: providerNumber,
},
};
if (apiStartDate < apiEndDate) {
storeActionConfigs.push(storeActionConfigInshop);
storeActionConfigs.push(storeActionConfigMobile);
}
if (apiStartDate < apiEndDate) storeActionConfigs.push(storeActionConfig);
}
const timeSlotsResponsesData = {
const inshopTimeSlotsData = {
days: [],
};
const mobileTimeSlotsData = {
days: [],
};
@ -167,39 +160,71 @@ const getAvailableDates = async (
const makeParallelCalls = async () => {
await Promise.all(
storeActionConfigs.map(async (storeAction) => {
const timeSlotsResponse = await baseMixin.methods.dispatchStoreActionWithLogging(
storeAction.storeAction,
storeAction.payload,
"schedule",
false
);
timeSlotsResponsesData.estimatedServiceMinutesMinimum =
timeSlotsResponse.data.estimatedServiceMinutesMinimum;
timeSlotsResponsesData.estimatedServiceMinutesMaximum =
timeSlotsResponse.data.estimatedServiceMinutesMaximum;
timeSlotsResponsesData.days = [
...timeSlotsResponsesData.days,
...timeSlotsResponse.data.days,
];
storeActionConfigs.map(async (storeActionConfig) => {
if (storeActionConfig.storeAction.indexOf("Mobile") > -1) {
const mobileTimeSlotsResponse =
await baseMixin.methods.dispatchStoreActionWithLogging(
storeActionConfig.storeAction,
storeActionConfig.payload,
"schedule",
false
);
mobileTimeSlotsData.estimatedServiceMinutesMinimum =
mobileTimeSlotsResponse.data.estimatedServiceMinutesMinimum;
mobileTimeSlotsData.estimatedServiceMinutesMaximum =
mobileTimeSlotsResponse.data.estimatedServiceMinutesMaximum;
mobileTimeSlotsData.days = [
...mobileTimeSlotsData.days,
...mobileTimeSlotsResponse.data.days,
];
} else {
const inshopTimeSlotsResponse =
await baseMixin.methods.dispatchStoreActionWithLogging(
storeActionConfig.storeAction,
storeActionConfig.payload,
"schedule",
false
);
inshopTimeSlotsData.estimatedServiceMinutesMinimum =
inshopTimeSlotsResponse.data.estimatedServiceMinutesMinimum;
inshopTimeSlotsData.estimatedServiceMinutesMaximum =
inshopTimeSlotsResponse.data.estimatedServiceMinutesMaximum;
inshopTimeSlotsData.days = [
...inshopTimeSlotsData.days,
...inshopTimeSlotsResponse.data.days,
];
}
})
);
};
return makeParallelCalls().then(() => {
// sort days chronologically
timeSlotsResponsesData.days.sort(compareDayStrings);
return timeSlotsResponsesData;
inshopTimeSlotsData.days.sort(compareDayStrings);
mobileTimeSlotsData.days.sort(compareDayStrings);
return {
inshopTimeSlotsData: inshopTimeSlotsData,
mobileTimeSlotsData: mobileTimeSlotsData,
};
});
};
const returnFirstDate = (dates) => {
if (dates.days?.length > 0) {
return dates.days[0].date;
} else {
return null;
}
};
export default {
name: "schedule",
data() {
return {
selectedDate: this.getSelectedDate(),
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
selectableDatesData: [],
mobilePremiumAppointmentFee: null,
waitListRequested: null,
displayWaitList: null,
@ -209,6 +234,9 @@ export default {
pricingByDayBasePrice: null,
pricingByDayUpcharge: null,
showPricingByDay: null,
selectableDatesInshop: [],
selectableDatesMobile: [],
isMobileSelected: false,
};
},
async beforeRouteEnter(to, from, next) {
@ -230,7 +258,6 @@ export default {
isRecalibrationOnOrder && shouldHideRecalibration
? getItemsWithoutRecalParts(lineItems.glassParts)
: (lineItems.glassParts ?? []);
const supportingItemsFromStore = lineItems?.supportingItems;
const supportingItemsWithoutFees = baseMixin.methods.filterOutCertainPartTypesOrNumbers(
lineItems.supportingItems,
{
@ -249,10 +276,14 @@ export default {
const priceString = getAmountDue(lineItemsToBePriced, false); // pass the IncludeTax param as false
const priceStringIntegerRoundedDown = priceString?.split(".")[0]; // same method used as getDisplayPrice() in service-package-radio used on /quote
const pricingByDayBasePrice = parseInt(priceStringIntegerRoundedDown);
const isMobileSelected =
store.getters.order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE;
let includePricingByDayUpcharge = false;
// Check to see if date should be pre-selected
let preSelectedSlot = await store.getters.order.schedule;
let preSelectedDate = preSelectedSlot ? preSelectedSlot.date : null;
if (isMobileSelected && preSelectedDate) preSelectedDate += "-mobile";
if (!preSelectedSlot.date || preSelectedSlot?.date?.length < 1) {
preSelectedSlot = null;
} else {
@ -279,9 +310,9 @@ export default {
const datePickerInitialDataPromise = await datePicker.methods.loadInitialData({
// setup config options for date-picker
selectableDatesSetting: "custom",
initialViewRowsToShow: 2,
customSelectableDatesCallback: getAvailableDates,
preSelectedDate: preSelectedSlot ? preSelectedSlot.date : preSelectedSlot,
initialViewRowsToShow: NUMBER_OF_CALENDAR_ROWS_TO_SHOW_FOR_INITIAL_VIEW,
getSelectableDatesCallback: getScheduleApiResponse,
preSelectedDate: preSelectedDate,
});
// Get pricingByDayUpcharge needed for Pricing By Day
@ -351,7 +382,10 @@ export default {
vm.setCmsContent(resultMap.cmsContent);
vm.$refs.datePicker.initializeComponent(datePickerInitialData);
vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons);
vm.selectableDatesData = datePickerInitialData.initialShopTimeSlotsResponse;
vm.selectableDatesInshop =
datePickerInitialData.initialShopTimeSlotsResponse.inshopTimeSlotsData;
vm.selectableDatesMobile =
datePickerInitialData.initialShopTimeSlotsResponse.mobileTimeSlotsData;
vm.mobilePremiumAppointmentFee = resultMap.premiumFeeWithPrice
? resultMap.premiumFeeWithPrice[0]
: null;
@ -363,21 +397,33 @@ export default {
vm.pricingByDayBasePrice = pricingByDayBasePrice;
vm.pricingByDayUpcharge = pricingByDayUpcharge;
vm.showPricingByDay = showPricingByDay;
vm.isMobileSelected = isMobileSelected;
});
},
mounted() {
this.$nextTick(() => {
const selectableDatesData = this.selectableDatesData;
// if no date selected on load
if (!this.selectedDate) {
if (selectableDatesData?.days?.length > 0) {
this.selectedDate = selectableDatesData.days[0].date;
// if no date is preselected on load, then select the first available
if (this.isMobileSelected) {
this.selectedDate = returnFirstDate(this.selectableDatesMobile);
} else {
this.selectedDate = returnFirstDate(this.selectableDatesInshop);
}
// if there is still no selected date, then load more and try again
if (!this.selectedDate) {
setTimeout(() => {
this.$refs.datePicker.showAnotherMonth().then((moreSelectableDatesData) => {
this.selectableDatesData = moreSelectableDatesData;
if (selectableDatesData?.days?.length > 0) {
this.selectedDate = selectableDatesData.days[0].date;
this.$refs.datePicker.showAnotherMonth().then((moreSelectableDates) => {
// update global
this.selectableDatesInshop.days =
moreSelectableDates.inshopTimeSlotsData.days;
this.selectableDatesMobile.days =
moreSelectableDates.mobileTimeSlotsData.days;
if (this.isMobileSelected) {
this.selectedDate = returnFirstDate(this.selectableDatesMobile);
} else {
this.selectedDate = returnFirstDate(this.selectableDatesInshop);
}
this.setDisplayWaitList();
@ -396,27 +442,46 @@ export default {
return this.splitCopyOnCMSPlaceHolder(this.ChangeShopLinkText);
},
appointmentType() {
return this.$store.getters.order.serviceLocation.appointmentType;
// TODO - THIS HAS TO BE CHANGED. APPOINTMENT TYPE CAN NOW CHANGE ON THE FLY, NOT FROM STORE ANYMORE.
// return this.$store.getters.order.serviceLocation.appointmentType;
if (this.isMobileSelected) {
return "Mobile";
} else {
return "Inshop";
}
},
getServiceMinutesMin() {
return this.isMobileSelected
? this.selectableDatesMobile.estimatedServiceMinutesMinimum
: this.selectableDatesInshop.estimatedServiceMinutesMinimum;
},
getServiceMinutesMax() {
return this.isMobileSelected
? this.selectableDatesMobile.estimatedServiceMinutesMaximum
: this.selectableDatesInshop.estimatedServiceMinutesMaximum;
},
timeSlotsForSelectedDate() {
if (!this.selectedDate) return null;
const selectedDate = this.selectedDate;
return this.selectableDatesData.days?.find(
(selectableDate) => selectableDate.date === this.selectedDate
);
if (this.isMobileSelected) {
const selectedDateString = selectedDate.replace("-mobile", "");
return this.selectableDatesMobile.days?.find(
(selectableDate) => selectableDate.date === selectedDateString
);
} else {
return this.selectableDatesInshop.days?.find(
(selectableDate) => selectableDate.date === this.selectedDate
);
}
},
},
methods: {
splitCopyOnCMSPlaceHolder,
arePagePrerequisitesValid() {
const serviceLocation = store.getters.order.serviceLocation;
const serviceLocation = store.getters.order.serviceLocation; // TODO - MAY NEED TO REMOVE THIS AS A PREREQ
const serviceLocationPreReqs =
serviceLocation.zipCode &&
serviceLocation.zipCodeCtu &&
serviceLocation.appointmentType &&
(serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE ||
serviceLocation.provider.providerNumber);
const serviceLocationPreReqs = serviceLocation.zipCode && serviceLocation.zipCodeCtu; // TODO - MAY NEED TO ADD IN SERVICE LOCATION PREREQS HERE TOO
const paymentInfo = store.getters.payment.isInsurance !== null;
@ -441,26 +506,38 @@ export default {
}
return preReqResult;
},
async getAvailableDatesMethod(startDate, endDate) {
const newShopTimeSlots = await getAvailableDates(
async getMoreScheduleData(startDate, endDate) {
// called only when "View more dates" is clicked; not on initial page load
const moreShopTimeSlots = await getScheduleApiResponse(
startDate,
endDate,
this.appointmentType,
this.$store.getters.order.serviceLocation.provider.providerNumber
);
// ADD API CALL RESULTS TO EXISTING DATE DATA
this.selectableDatesData.days = this.selectableDatesData.days.concat(
newShopTimeSlots.days
this.selectableDatesInshop.days = this.selectableDatesInshop.days.concat(
moreShopTimeSlots.inshopTimeSlotsData.days
);
return newShopTimeSlots;
this.selectableDatesMobile.days = this.selectableDatesMobile.days.concat(
moreShopTimeSlots.mobileTimeSlotsData.days
);
return moreShopTimeSlots;
},
getAvailableDates,
getScheduleApiResponse,
getServiceZipCtuCodeFromStore() {
return store.getters.order.serviceLocation.zipCodeCtu;
},
getSelectedDate() {
return store.getters.order.schedule.date;
let isMobileSelected = this.isMobileSelected;
if (isMobileSelected === undefined) {
isMobileSelected =
store.getters.order.serviceLocation.appointmentType ===
AppointmentTypeStrings.MOBILE;
}
let storedDate = store.getters.order.schedule.date;
if (isMobileSelected) storedDate += "-mobile";
return storedDate;
},
getSelectedTimeSlotInfo() {
const supportingItems = this.getSupportingItems();
@ -513,8 +590,7 @@ export default {
},
convertSelectedDateToShortMonthAndDay(selectedDate) {
// This conversion ensures we don't get get GMT induced date changes
const dateObject = convertDateStringToDate(selectedDate);
// Ex: April 25
const dateObject = convertDateStringToDate(selectedDate.replace("-mobile", ""));
return dateObject.toLocaleDateString("en-us", { month: "short", day: "numeric" });
},
getDisplayTextForMilitaryTime(militaryTimeInput, shouldTrimMinutesIfEmpty = false) {
@ -523,10 +599,7 @@ export default {
const minutes = militaryTimeInput.split(":")[1];
const meridianNotation = hours > 11 ? "PM" : "AM";
if (hours > 12) {
hours -= 12;
}
if (hours > 12) hours -= 12;
if (shouldTrimMinutesIfEmpty && minutes === "00") {
return `${hours} ${meridianNotation}`;
} else {
@ -540,15 +613,25 @@ export default {
);
},
forwardButtonAction() {
// TODO - WILL NEED TO SAVE THE APPOINTMENT TYPE ALSO (AND ANYTHING ELSE THAT SERVICE-LOCATION DID TOO)
this.updateSupportingItems();
if (this.waitListRequested) {
var gaLabel = store.getters.isMobileAppointment
? "checked_mobile"
: "checked_inshop";
if (this.displayWaitList) {
let gaLabel = "";
let status = this.waitListRequested ? "checked" : "unchecked";
if (
this.waitListRequested === null &&
this.$store.state.order.customer.waitListRequested === true
) {
status = "checked";
}
const type = store.getters.isMobileAppointment ? "mobile" : "inshop";
gaLabel = `${status}_${type}`;
const currentDate = new Date();
const dateString = this.selectableDatesData.days[0].date;
const dateString = this.isMobileSelected
? this.selectableDatesMobile.days[0].date
: this.selectableDatesInshop.days[0].date;
const [year, month, day] = dateString.split("-").map(Number);
const appointmentDate = new Date(year, month - 1, day);
const timeDifference = appointmentDate - currentDate;
@ -565,11 +648,14 @@ export default {
store.getters.order.serviceLocation.zipCodeCtu;
this.pushEventToGA("waitlist", "add_to_waitlist_check_box_status", gaLabel, true);
}
if (this.selectedTimeSlotInfo.timeSlot.jobMinMinutes == null) {
this.selectedTimeSlotInfo.timeSlot.jobMinMinutes =
this.selectableDatesData?.estimatedServiceMinutesMinimum?.toString();
this.selectedTimeSlotInfo.timeSlot.jobMaxMinutes =
this.selectableDatesData?.estimatedServiceMinutesMaximum?.toString();
this.selectedTimeSlotInfo.timeSlot.jobMinMinutes = this.isMobileSelected
? this.selectableDatesMobile.estimatedServiceMinutesMinimum?.toString()
: this.selectableDatesInshop.estimatedServiceMinutesMinimum?.toString();
this.selectedTimeSlotInfo.timeSlot.jobMaxMinutes = this.isMobileSelected
? this.selectableDatesMobile.estimatedServiceMinutesMaximum?.toString()
: this.selectableDatesInshop.estimatedServiceMinutesMaximum?.toString();
}
this.dispatchStoreAction(
@ -597,9 +683,12 @@ export default {
experimentSettings.DISPLAY_WAITLIST,
"true"
) &&
this.selectableDatesData.days[0]
this.selectableDatesInshop.days[0] && // TODO - ASK BUSINESS... WHAT LOGIC SHOULD WE USE HERE FOR WHEN TO DISPLAY THE WAIT LIST?
this.selectableDatesMobile.days[0]
) {
const dateString = this.selectableDatesData.days[0].date;
const dateString = this.isMobileSelected
? this.selectableDatesMobile.days[0].date
: this.selectableDatesInshop.days[0].date;
const [year, month, day] = dateString.split("-").map(Number);
const targetDate = new Date(year, month - 1, day);
const currentDate = new Date();
@ -765,7 +854,7 @@ export default {
}
}
.change-location a {
font-family: AvertaSemibold;
font-family: UrbanistSemibold;
}
.time-slots-question {
padding: 0 0.75rem;

View file

@ -631,7 +631,6 @@ export default {
margin-left: 0.5rem;
strong {
font-weight: 600;
letter-spacing: 0%;
}
}
.success-image {

View file

@ -561,7 +561,7 @@ export default {
margin-top: 1.5rem;
.time-slot-header {
font-family: AvertaBold;
font-family: UrbanistBold;
margin-top: 1.5rem;
}
.text-block.duration-text-block {

View file

@ -96,7 +96,6 @@ import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
import baseMixin from "@/mixins/base-mixin.js";
import { queryStrings } from "@/constants/query-strings";
import { routeData } from "@/router/constants/routes";
import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stash";
// Define Validation Rules
defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
@ -116,7 +115,7 @@ export default {
mixins: [vinPagesMixin],
data() {
return {
serviceZipCode: this.getZipFromStore() ?? consumeQueryFromStash(queryStrings.ZIP_CODE),
serviceZipCode: this.getZipFromStore(),
emailOrSms: this.getEmailOrSmsFromStore(),
carId: this.getCarIdfromStore(),
isHeavyTruck: this.getIsVehicleHeavyTruckServiceableFromStore(),

View file

@ -110,10 +110,6 @@ import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
import { applicationConfig } from "../../constants/application-config";
import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import {
consumeQueryFromStash,
peekQueryFromStash,
} from "@/router/methods/helpers/querystring-stash";
import { debugLog } from "@/helpers/debug-log-helper";
//define validation rules
@ -138,7 +134,7 @@ export default {
imageUrl: this.getImagefromStore(),
imageVifNumber: this.getImageVifNumberfromStore(),
imageVifColor: this.getImageVifColorfromStore(),
serviceZipCode: this.getZipFromStore() ?? peekQueryFromStash(queryStrings.ZIP_CODE),
serviceZipCode: this.getZipFromStore(),
yearOptions: [],
makeOptions: [],
modelOptions: [],

View file

@ -159,8 +159,6 @@ import baseMixin from "@/mixins/base-mixin.js";
import store from "@/store";
import vinPagesMixin from "@/mixins/vin-pages-mixin";
import { routeData } from "@/router/constants/routes";
import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stash";
import { queryStrings } from "@/constants/query-strings";
// DEFINE VALIDATION RULES
defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
@ -204,7 +202,7 @@ export default {
data() {
return {
vin: this.getVinFromStore(),
serviceZipCode: this.getZipFromStore() ?? consumeQueryFromStash(queryStrings.ZIP_CODE),
serviceZipCode: this.getZipFromStore(),
emailOrSms: this.getEmailOrSmsFromStore(),
carId: this.getCarIdfromStore(),
isHeavyTruck: this.getIsVehicleHeavyTruckServiceableFromStore(),

View file

@ -4,6 +4,10 @@ import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
import { routeData } from "@/router/constants/routes";
import { isVerifiedInsurance } from "@/router/methods/helpers/requires-verified-redirect";
import store from "@/store";
import { queryStrings } from "@/constants/query-strings";
import baseMixin from "@/mixins/base-mixin";
import { storeActions } from "@/constants/store-actions";
import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stash";
export async function vehicleBeforeEnter(to, from) {
// If cookie indicates that vehicle has changed during insurance flow, reset parts info.
@ -12,6 +16,23 @@ export async function vehicleBeforeEnter(to, from) {
store.commit(storeMutations.RESET_GLASS_PARTS_STATE);
}
// If there is a zip querystring, consume & commit it here.
const newZipFromQuerystring = consumeQueryFromStash(queryStrings.ZIP_CODE);
if (newZipFromQuerystring) {
const zipData = await baseMixin.methods.getZipCodeData(
newZipFromQuerystring,
routeData.VEHICLE.name
);
if (zipData.isValid) {
store.dispatch(storeActions.SAVE_SERVICE_ZIP_CODE_INFO, {
zipCode: newZipFromQuerystring,
state: zipData.state.state,
zipCodeCtu: zipData.zipCodeCtu,
});
}
}
if (isVerifiedInsurance()) {
return {
name: routeData.VEHICLE_DAMAGE.name,

View file

@ -97,3 +97,11 @@ body {
}
}
}
button,
input,
select,
textarea,
option,
.btn {
letter-spacing: 0.03rem;
}

View file

@ -4,6 +4,7 @@ body {
font-size: 1rem;
line-height: 1.625;
font-weight: 400;
letter-spacing: 0.03rem;
}
//Headings
@ -43,7 +44,7 @@ h5,
h6,
.h6 {
line-height: 1.7;
letter-spacing: 0.75px;
letter-spacing: 0.03rem;
text-transform: uppercase;
}
@ -78,58 +79,58 @@ caption,
font-weight: 500;
}
// Averta Fonts
// Urbanist Fonts
@font-face {
font-family: "AvertaBold";
font-family: "UrbanistRegular";
src:
url("@/assets/fonts/averta-bold-webfont.woff2") format("woff2"),
url("@/assets/fonts/averta-bold-webfont.woff") format("woff");
url("@/assets/fonts/Urbanist-Regular.woff") format("woff2"),
url("@/assets/fonts/Urbanist-Regular.woff2") format("woff");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "AvertaBold_Italic";
font-family: "UrbanistSemibold";
src:
url("@/assets/fonts/averta-bolditalic-webfont.woff2") format("woff2"),
url("@/assets/fonts/averta-bolditalic-webfont.woff") format("woff");
url("@/assets/fonts/Urbanist-SemiBold.woff") format("woff2"),
url("@/assets/fonts/Urbanist-SemiBold.woff2") format("woff");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "AvertaExtraBold";
font-family: "UrbanistBold";
src:
url("@/assets/fonts/averta-extrabold-webfont.woff2") format("woff2"),
url("@/assets/fonts/averta-extrabold-webfont.woff") format("woff");
url("@/assets/fonts/Urbanist-Bold.woff") format("woff2"),
url("@/assets/fonts/Urbanist-Bold.woff2") format("woff");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "AvertaRegular";
font-family: "UrbanistExtraBold";
src:
url("@/assets/fonts/averta-regular-webfont.woff2") format("woff2"),
url("@/assets/fonts/averta-regular-webfont.woff") format("woff");
url("@/assets/fonts/Urbanist-ExtraBold.woff") format("woff2"),
url("@/assets/fonts/Urbanist-ExtraBold.woff2") format("woff");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "AvertaRegular_Italic";
font-family: "UrbanistMedium_Italic";
src:
url("@/assets/fonts/averta-regularitalic-webfont.woff2") format("woff2"),
url("@/assets/fonts/averta-regularitalic-webfont.woff") format("woff");
url("@/assets/fonts/Urbanist-MediumItalic.woff") format("woff2"),
url("@/assets/fonts/Urbanist-MediumItalic.woff2") format("woff");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "AvertaSemibold";
font-family: "UrbanistBold_Italic";
src:
url("@/assets/fonts/averta-semibold-webfont.woff2") format("woff2"),
url("@/assets/fonts/averta-semibold-webfont.woff") format("woff");
url("@/assets/fonts/Urbanist-BoldItalic.woff") format("woff2"),
url("@/assets/fonts/Urbanist-BoldItalic.woff2") format("woff");
font-weight: normal;
font-style: normal;
}

View file

@ -120,9 +120,9 @@ $theme-colors: (
$body-color: $gray-600;
//Fonts
$font-family-sans-serif: AvertaRegular, Arial, Helvetica, sans-serif;
$font-family-sans-serif: UrbanistRegular, Arial, Helvetica, sans-serif;
$font-family-monospace:
SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
UrbanistRegular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
// stylelint-enable value-keyword-case
$font-family-base: $font-family-sans-serif;
$font-family-code: $font-family-monospace;

View file

@ -103,6 +103,7 @@ export default {
justify-content: flex-start;
p {
font-family: UrbanistRegular;
color: $gray-600;
text-align: center;
@ -123,15 +124,16 @@ export default {
&:checked + .list-card-content {
p {
color: $black;
font-weight: 500;
font-family: UrbanistSemibold;
}
.p-bold {
font-family: UrbanistSemibold;
font-weight: 600;
}
.sub-copy {
font-family: UrbanistSemibold;
color: $gray-600;
font-weight: 400;
}
}