Merge branch 'develop' into feature/jzimmerman/INSR-8742

This commit is contained in:
Jeremy-Z 2026-03-06 11:48:13 -05:00
commit f69cc5b176
10 changed files with 273 additions and 300 deletions

View file

@ -171,23 +171,38 @@ body .buttonContainer .btn-success:focus, body .buttonContainer .btn-success:act
background-color: #0c7e47;
box-shadow: 0 0 0 3px #ffffff, 0 0 0 5.5px #0c7e47;
}
body .has-error input,
body .has-error select {
body .has-error input.form-control,
body .has-error select.form-control {
border-color: #db0020;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
body .has-error input.form-control:focus,
body .has-error select.form-control:focus {
box-shadow: none;
}
body .has-error select {
background-image: url(/icons/select-error.png);
}
body .has-error small {
font-size: 1rem;
line-height: 1.56;
margin-bottom: 0.3125rem;
color: #db0020;
}
body .has-success input,
body .has-success select {
body .has-success .control-label {
color: #4d4e53;
}
body .has-success input.form-control,
body .has-success select.form-control {
border-color: #0c7e47;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
body .has-success input.form-control:focus,
body .has-success select.form-control:focus {
box-shadow: none;
}
body .has-success select {
background-image: url(/icons/select-error.png);
background-image: url(/icons/select-success.png);
}
body .cc-error-container #alert-message {
background-color: #fcbfbb;
@ -337,17 +352,6 @@ body .cc-error-container #alert-message {
}
}
.glyphicon {
position: relative;
top: 1px;
display: inline-block;
font-family: Glyphicons Halflings;
font-style: normal;
font-weight: normal;
line-height: 1;
-webkit-font-smoothing: antialiased;
}
.form-control-feedback {
position: absolute;
top: 0;
@ -361,11 +365,12 @@ body .cc-error-container #alert-message {
pointer-events: none;
top: 27px;
}
.glyphicon-remove:before {
.form-control-feedback.glyphicon {
display: none;
}
.form-control-feedback.glyphicon-remove:before {
content: "\e014";
}
.glyphicon-ok:before {
.form-control-feedback.glyphicon-ok:before {
content: "\e013";
}

View file

@ -4,95 +4,12 @@
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
/* Style the modal when PayPal button is selected */
#pageLoadingModal {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1040;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.4);
display: flex;
}
#pageLoadingModal .modal-content {
margin: auto auto;
}
#pageLoadingModal p {
margin: 0;
text-transform: uppercase;
font-size: 14px;
}
#pageLoadingModal .modal {
display: none;
overflow: hidden;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1040;
-webkit-overflow-scrolling: touch;
outline: 0;
}
#pageLoadingModal .modal-content {
position: relative;
background-color: #fff;
border-radius: 8px;
background-clip: padding-box;
outline: 0;
width: 176px;
height: 136px;
display: flex;
justify-content: center;
align-items: center;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='none' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 84 32'%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' fill='%23fff'/%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' fill='%23fff'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06.8c8.08 0 11.89.935 11.89.935 3.58.576 5.696 7.207 5.696 7.207h.727c0-2.427 1.302-2.397 2.341-2 .723.292 1.363.76 1.86 1.361 1.319 1.547.465 1.674.465 1.674h-5.067l3.846 3.01v12.016c0 2.41-2.029 2.31-2.029 2.31H22.338s-2.029.1-2.029-2.31V12.996l3.84-3.009H19.07s-.853-.127.466-1.674a4.693 4.693 0 0 1 1.86-1.361c1.032-.397 2.341-.427 2.341 2h.736s2.117-6.64 5.696-7.21c0 0 3.81-.942 11.89-.942Z' fill='%23fff' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06 8.847c7.924 0 14.685.511 14.685.511 0-2.585-2.38-6.011-2.38-6.011S50.4 2.519 42.06 2.519s-12.303.828-12.303.828-2.38 3.426-2.38 6.011c0 0 6.76-.51 14.683-.51Z' fill='%23DA291C' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M35.277 16.523a62.558 62.558 0 0 1 13.277 0m3.276-.439s2.384-2.257 8.608-2.257c0 0 1.179 2.657-2.54 3.37m-25.894-1.113s-2.387-2.257-8.611-2.257c0 0-1.16 2.579 2.543 3.37m-3.546 5.856s21.52 3.647 39.123 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: center center;
background-size: 96px;
}
#pageLoadingModal .modal-content:after {
content: "";
border-radius: 50%;
position: absolute;
width: 5.5rem;
height: 5.5rem;
border: 0.45em solid #d4281c;
border-right-color: transparent;
animation: spinnerrotate 0.75s linear infinite;
}
@keyframes spinnerrotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
#pageLoadingModal .modal-content p {
display: none;
}
#pageLoadingModal .modal-backdrop {
position: absolute;
top: 0;
right: 0;
left: 0;
background-color: #000000;
}
#pageLoadingModal .modal-backdrop.fade {
opacity: 0;
filter: alpha(opacity=0);
}
#pageLoadingModal.hide {
display: none;
}
.buy-backdrop {
background-color: rgba(0, 0, 0, 0.8) !important;
}
.buy-backdrop #afterpay__iframe-checkout-container {
width: 100%;
}
.buy-backdrop #afterpay__iframe-checkout-container .style_closeWrapper__lIHJe {
display: none;
}

View file

@ -198,12 +198,20 @@ body {
.has-error {
input,
select {
border-color: #db0020;
&.form-control {
border-color: #db0020;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
&:focus {
box-shadow: none;
}
}
}
select {
background-image: url(/icons/select-error.png);
}
small {
font-size: 1rem;
line-height: 1.56;
margin-bottom: .3125rem;
color: #db0020;
}
@ -211,9 +219,18 @@ body {
.has-success {
.control-label {
color: #4d4e53
}
input,
select {
border-color: #0c7e47;
&.form-control {
border-color: #0c7e47;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
&:focus {
box-shadow: none;
}
}
}
select {
background-image: url(/icons/select-success.png);
@ -442,17 +459,6 @@ body {
}
}
.glyphicon {
position: relative;
top: 1px;
display: inline-block;
font-family: Glyphicons Halflings;
font-style: normal;
font-weight: normal;
line-height: 1;
-webkit-font-smoothing: antialiased;
}
.form-control-feedback {
position: absolute;
top: 0;
@ -465,10 +471,13 @@ body {
text-align: center;
pointer-events: none;
top: 27px;
}
.glyphicon-remove:before {
content: "\e014";
}
.glyphicon-ok:before {
content: "\e013";
&.glyphicon {
display: none;
}
&.glyphicon-remove:before {
content: "\e014";
}
&.glyphicon-ok:before {
content: "\e013";
}
}

View file

@ -5,89 +5,7 @@
/* Style the modal when PayPal button is selected */
#pageLoadingModal {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1040;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.40);
display: flex;
.modal-content {
margin: auto auto;
}
p {
margin: 0;
text-transform: uppercase;
font-size: 14px;
}
.modal {
display: none;
overflow: hidden;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1040;
-webkit-overflow-scrolling: touch;
outline: 0;
}
.modal-content {
position: relative;
background-color: #fff;
border-radius: 8px;
background-clip: padding-box;
outline: 0;
width: 176px;
height: 136px;
display: flex;
justify-content: center;
align-items: center;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='none' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 84 32'%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' fill='%23fff'/%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' fill='%23fff'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06.8c8.08 0 11.89.935 11.89.935 3.58.576 5.696 7.207 5.696 7.207h.727c0-2.427 1.302-2.397 2.341-2 .723.292 1.363.76 1.86 1.361 1.319 1.547.465 1.674.465 1.674h-5.067l3.846 3.01v12.016c0 2.41-2.029 2.31-2.029 2.31H22.338s-2.029.1-2.029-2.31V12.996l3.84-3.009H19.07s-.853-.127.466-1.674a4.693 4.693 0 0 1 1.86-1.361c1.032-.397 2.341-.427 2.341 2h.736s2.117-6.64 5.696-7.21c0 0 3.81-.942 11.89-.942Z' fill='%23fff' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06 8.847c7.924 0 14.685.511 14.685.511 0-2.585-2.38-6.011-2.38-6.011S50.4 2.519 42.06 2.519s-12.303.828-12.303.828-2.38 3.426-2.38 6.011c0 0 6.76-.51 14.683-.51Z' fill='%23DA291C' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M35.277 16.523a62.558 62.558 0 0 1 13.277 0m3.276-.439s2.384-2.257 8.608-2.257c0 0 1.179 2.657-2.54 3.37m-25.894-1.113s-2.387-2.257-8.611-2.257c0 0-1.16 2.579 2.543 3.37m-3.546 5.856s21.52 3.647 39.123 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: center center;
background-size: 96px;
&:after {
content: '';
border-radius: 50%;
position: absolute;
width: 5.5rem;
height: 5.5rem;
border: 0.45em solid #d4281c;
border-right-color: transparent;
animation: spinnerrotate 0.75s linear infinite;
}
@keyframes spinnerrotate {
0% { transform: rotate(0)}
100% { transform: rotate(360deg)}
}
p {
display: none;
}
}
.modal-backdrop {
position: absolute;
top: 0;
right: 0;
left: 0;
background-color: #000000;
&.fade {
opacity: 0;
filter: alpha(opacity=0);
}
}
&.hide {
display: none;
}
display: none;
}
// AfterPay Styles
@ -95,7 +13,6 @@
.buy-backdrop {
background-color: rgba(0,0,0,.8)!important;
#afterpay__iframe-checkout-container {
width: 100%;
.style_closeWrapper__lIHJe {
display: none;
}

View file

@ -10,11 +10,69 @@ import { getRandomString, getRandomInt, getRandomBoolean } from '@/helpers/data-
import navigationScenarios from '@/router/router-constants/navigation-scenarios.js';
import { useMainStore } from '@/store/index.js';
/** @ignore */
function setupMocks({
storeData,
props
}) {
const mountOptions = getMountOptions({
router: {
navigate: jest.fn(),
navigateWithSpinner: jest.fn()
},
loadScript: jest.fn().mockResolvedValue()
});
if (storeData) mountOptions.global.plugins = [createTestingPinia({
initialState: {
main: storeData
}
})];
window.google = {
maps: {
event: {
addListener: jest
.fn()
.mockImplementation((element, eventName, callbackFunction) => {
/** @ignore */
function interceptedCallbackFunction(e) {
callbackFunction(e.detail);
}
// selectedPlace = "Woogly";
element.addEventListener(eventName, interceptedCallbackFunction);
}),
removeListener: jest.fn(),
clearInstanceListeners: jest.fn()
},
places: {
Autocomplete: jest.fn().mockImplementation((el) => el),
AutocompleteService: jest.fn().mockImplementation(() => {
return {
getPlacePredictions: (request, callback) => {
// Throwing an error here guarantees that the function that calls it finishes
throw new Error();
}
};
}),
PlacesService: jest.fn().mockImplementation(() => {}),
AutocompleteSessionToken: jest.fn().mockImplementation(() => {})
}
}
};
if (props) mountOptions.propsData = props;
const wrapper = shallowMount(contactDetails, mountOptions);
return { wrapper };
}
describe('contactDetails.vue', () => {
describe('Rendering', () => {
test('Should render site header', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const siteHeader = wrapper.findComponent({ ref: 'siteHeader' });
@ -24,7 +82,7 @@ describe('contactDetails.vue', () => {
});
test('Should render sub title', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const siteSubHeader = wrapper.findComponent({ ref: 'siteSubHeader' });
@ -34,7 +92,7 @@ describe('contactDetails.vue', () => {
});
test('Should render appointment information alert', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const appointmentInformationAlert = wrapper.findComponent({ ref: 'appointmentInformationAlert' });
@ -44,8 +102,6 @@ describe('contactDetails.vue', () => {
});
test('Should render same as policy address question subcomponent if service zip is same as customer zip', async () => {
// Arrange
const mountOptions = getMountOptions();
const customerStreetAddress = getRandomString(10, 50);
const customerStreetAddress2 = getRandomString(0, 50);
const customerCity = getRandomString(4, 20);
@ -55,7 +111,7 @@ describe('contactDetails.vue', () => {
const serviceCity = getRandomString(4, 20);
const serviceState = getRandomString(2, 2);
const zipCode = getRandomInt(10000, 99999).toString();
const mainInitialState = {
const storeData = {
order: {
customer: {
address: {
@ -75,12 +131,7 @@ describe('contactDetails.vue', () => {
}
}
};
mountOptions.global.plugins = [createTestingPinia({
initialState: {
main: mainInitialState
}
})];
const wrapper = shallowMount(contactDetails, mountOptions);
const { wrapper } = setupMocks({ storeData });
await wrapper.vm.$nextTick();
@ -92,8 +143,6 @@ describe('contactDetails.vue', () => {
});
test('Should not render same as policy address question subcomponent if service zip is different from customer zip', () => {
// Arrange
const mountOptions = getMountOptions();
const firstName = getRandomString(4, 15);
const lastName = getRandomString(4, 15);
const emailAddress = getRandomString(10, 20);
@ -101,7 +150,7 @@ describe('contactDetails.vue', () => {
const testZipCode = getRandomInt(10000, 99999);
const customerZipCode = testZipCode.toString();
const serviceZipCode = (testZipCode + 1).toString();
const mainInitialState = {
const storeData = {
order: {
customer: {
firstName,
@ -119,12 +168,7 @@ describe('contactDetails.vue', () => {
}
}
};
mountOptions.global.plugins = [createTestingPinia({
initialState: {
main: mainInitialState
}
})];
const wrapper = shallowMount(contactDetails, mountOptions);
const { wrapper } = setupMocks({ storeData });
// Act
const sameAsPolicyAddressQuestion = wrapper.findComponent({ ref: 'sameAsPolicyAddressQuestion' });
@ -134,7 +178,7 @@ describe('contactDetails.vue', () => {
});
test('Should render address question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const addressQuestion = wrapper.findComponent({ ref: 'addressQuestion' });
@ -144,7 +188,7 @@ describe('contactDetails.vue', () => {
});
test('Should render apartment question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const address2Question = wrapper.findComponent({ ref: 'address2Question' });
@ -154,7 +198,7 @@ describe('contactDetails.vue', () => {
});
test('Should render city question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const cityQuestion = wrapper.findComponent({ ref: 'cityQuestion' });
@ -164,7 +208,7 @@ describe('contactDetails.vue', () => {
});
test('Should render state question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const stateQuestion = wrapper.findComponent({ ref: 'stateQuestion' });
@ -174,7 +218,7 @@ describe('contactDetails.vue', () => {
});
test('Should render zip code question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const zipCodeQuestion = wrapper.findComponent({ ref: 'zipCodeQuestion' });
@ -184,7 +228,7 @@ describe('contactDetails.vue', () => {
});
test('Should render change zip code alert', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const changeZipCodeAlert = wrapper.findComponent({ ref: 'changeZipCodeAlert' });
@ -194,7 +238,7 @@ describe('contactDetails.vue', () => {
});
test('Should render vehicle protected question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const vehicleProtectedQuestion = wrapper.findComponent({ ref: 'vehicleProtectedQuestion' });
@ -204,7 +248,7 @@ describe('contactDetails.vue', () => {
});
test('Should render technician notes textarea question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const notesQuestion = wrapper.findComponent({ ref: 'notesQuestion' });
@ -214,7 +258,7 @@ describe('contactDetails.vue', () => {
});
test('Should render clearance text subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const clearanceText = wrapper.findComponent({ ref: 'clearanceText' });
@ -224,7 +268,7 @@ describe('contactDetails.vue', () => {
});
test('Should render site footer', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const footer = wrapper.findComponent({ ref: 'siteFooter' });
@ -234,8 +278,6 @@ describe('contactDetails.vue', () => {
});
test('Mocked store yields expected data', () => {
// Arrange
const mountOptions = getMountOptions();
const address = getRandomString(10, 50);
const address2 = getRandomString(0, 50);
const city = getRandomString(4, 20);
@ -243,7 +285,7 @@ describe('contactDetails.vue', () => {
const zipCode = getRandomInt(10000, 99999).toString();
const notesForTechnician = getRandomString(1, 100);
const isVehicleProtected = getRandomBoolean();
const mainInitialState = {
const storeData = {
order: {
serviceLocation: {
address,
@ -258,13 +300,8 @@ describe('contactDetails.vue', () => {
}
}
};
mountOptions.global.plugins = [createTestingPinia({
initialState: {
main: mainInitialState
}
})];
const wrapper = shallowMount(contactDetails, mountOptions);
const { wrapper } = setupMocks({ storeData });
// Assert
expect(wrapper.vm.address).toBe(address);
@ -280,11 +317,7 @@ describe('contactDetails.vue', () => {
describe('Navigation', () => {
test('Back button clicked triggers navigation', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions({
router: {
navigateWithSpinner: jest.fn()
}
}));
const { wrapper } = setupMocks({});
wrapper.vm.navigateBack = baseMixin.methods.navigateBack;
// Act
@ -295,41 +328,25 @@ describe('contactDetails.vue', () => {
expect(wrapper.vm.$router.navigateWithSpinner)
.toHaveBeenCalledWith(navigationScenarios.CLICKED_BACK, undefined);
});
test('Forward button clicked triggers appropriate navigation', () => {
test('Forward button clicked triggers appropriate navigation', async () => {
// Arrange
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
},
navigationScenarios
});
const mainInitialState = {
const storeData = {
order: {
serviceLocation: { IsSafeliteProvider: true }
}
};
mountOptions.global.plugins = [createTestingPinia({
initialState: {
main: mainInitialState
}
})];
const wrapper = shallowMount(contactDetails, mountOptions);
const { wrapper } = setupMocks({ storeData });
// Act
wrapper.vm.forwardButtonAction();
await wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
expect(wrapper.vm.$router.navigate)
.toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD, undefined);
});
test('Forward button click updates service location info', () => {
test('Forward button click updates service location info', async () => {
// Arrange
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
}
});
const provider = {
address: {
city: getRandomString(4, 20),
@ -342,19 +359,14 @@ describe('contactDetails.vue', () => {
phoneNumber: getRandomInt(1000000000, 9999999999).toString(),
providerNumber: getRandomInt(100000, 999999).toString()
}
const mainInitialState = {
const storeData = {
order: {
serviceLocation: {
provider
}
}
};
mountOptions.global.plugins = [createTestingPinia({
initialState: {
main: mainInitialState
}
})];
const wrapper = shallowMount(contactDetails, mountOptions);
const { wrapper } = setupMocks({ storeData });
const address = getRandomString(10, 50);
const address2 = getRandomString(0, 50);
const city = getRandomString(4, 20);
@ -371,7 +383,7 @@ describe('contactDetails.vue', () => {
});
// Act
wrapper.vm.forwardButtonAction();
await wrapper.vm.forwardButtonAction();
// Assert
expect(useMainStore().updateServiceLocation).toHaveBeenCalledWith({
@ -383,21 +395,16 @@ describe('contactDetails.vue', () => {
});
});
test('Forward button click updates notes for technician', () => {
test('Forward button click updates notes for technician', async () => {
// Arrange
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
}
});
const wrapper = shallowMount(contactDetails, mountOptions);
const { wrapper } = setupMocks({});
const notesForTechnician = getRandomString(1, 100);
wrapper.setData({
notesForTechnician
});
// Act
wrapper.vm.forwardButtonAction();
await wrapper.vm.forwardButtonAction();
// Assert
expect(useMainStore().updateContactInfo).toHaveBeenCalledWith({

View file

@ -133,6 +133,7 @@ import errorMessages from '@/constants/error-messages';
import { required } from '@/helpers/validation-rules';
import widgetFields from '@/constants/cms-widget-fields';
import states from '@/constants/states';
import applicationConfig from '@/constants/application-config';
// DEFINE VALIDATION RULES
defineRule('street-address-required', required(errorMessages.SERVICE_ADDRESS_REQUIRED));
@ -157,8 +158,9 @@ export default {
async beforeRouteEnter(to, from, next) {
const cmsContent = await fetchCmsContentForPage(to.query.issPage);
next((vm) => {
next(async (vm) => {
vm.setCmsContent(cmsContent);
await vm.setupAddressLookup();
});
},
data() {
@ -239,7 +241,7 @@ export default {
/**
* @summary Steps to perform when forward button clicked.
*/
forwardButtonAction() {
async forwardButtonAction() {
const contactInfo = {
notesForTechnician: this.notesForTechnician
};
@ -247,6 +249,8 @@ export default {
const provider = useMainStore().serviceLocation.provider;
await this.geocodeAddress();
useMainStore().updateServiceLocation({
address: this.address,
address2: this.address2,
@ -267,7 +271,100 @@ export default {
this.address = '';
this.address2 = '';
this.city = '';
}
},
async setupAddressLookup() {
const apiKey = applicationConfig.GOOGLE_PLACES_API_KEY;
await this.$loadScript(`https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places&callback=Function.prototype`)
.catch(() => {
// Failed to fetch script
window.console.warn('Unable to load Google Places API script');
});
},
geocodeAddress() {
const autoCompletePromise = new Promise((resolve, reject) => {
try {
// Get Autocomplete Service
const acService = new window.google.maps.places.AutocompleteService();
// Get Places Service, needs pseudo element (or a map)
const placeService = new window.google.maps.places.PlacesService(document.createElement('div'));
// Create Autocomplete Session token, multiple requests one pricing hit
const acSessionToken = new window.google.maps.places.AutocompleteSessionToken();
const addressValue = `${this.address}, ${this.city}, ${this.state} ${this.zipCode}`;
acService.getPlacePredictions(
{
input: addressValue,
type: ['geocode'],
componentRestrictions: { country: ['us'] },
sessionToken: acSessionToken
},
(predictions) => {
if (predictions && predictions.length > 0) {
const firstPrediction = predictions[0];
if (firstPrediction.place_id) {
placeService.getDetails(
{
placeId: firstPrediction.place_id,
fields: ['address_components'],
sessionToken: acSessionToken
},
(details) => {
this.fillInAddress(details);
resolve();
}
);
} else {
resolve();
}
} else {
resolve();
}
}
);
} catch (error) {
window.console.warn('Error initializing Google Places API services', error);
resolve();
}
});
return autoCompletePromise;
},
async fillInAddress(googlePlace) {
let processedStreetAddress = false;
let processedRoute = false;
for (const component of googlePlace.address_components) {
const componentType = component.types[0];
switch (componentType) {
case 'street_number': {
if (processedRoute) {
this.address = `${component.long_name} ${this.address}`;
} else {
this.address = component.long_name;
}
processedStreetAddress = true;
break;
}
case 'route': {
if (processedStreetAddress) {
this.address += ` ${component.short_name}`;
} else {
this.address = component.short_name;
}
processedRoute = true;
break;
}
case 'locality': {
this.city = component.long_name;
break;
}
default:
}
}
},
}
};
</script>

View file

@ -389,7 +389,6 @@ export default {
}
this.smsPhoneNumber = this.getSMSPhoneFromStore();
if (this.isPayInAdvanceDisabled) {
console.log('Pay in Advance is disabled, defaulting to Pay at Time of Service');
this.paymentMethod = paymentMethods.PAY_AT_TIME_OF_SERVICE;
}
},
@ -442,8 +441,11 @@ export default {
handleEditClicked(section) {
switch (section) {
case 'location':
const scenario = this.mainStore.isMobileAppointment
? this.navigationScenarios.EDIT_SERVICE_LOCATION_MOBILE
: this.navigationScenarios.EDIT_SERVICE_LOCATION_INSHOP;
this.$router.navigateWithSpinner(
this.navigationScenarios.EDIT_SERVICE_LOCATION,
scenario,
this.$route
);
break;

View file

@ -470,7 +470,7 @@ export default {
const paymentType = useMainStore().order.payment.paymentMethod;
const alertToDisplay = to.query[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT];
if (paymentType === paymentMethods.PAYPAL || alertToDisplay) {
useMainStore().savePaymentMethodChoice(paymentMethods.CREDIT_CARD);
useMainStore().savePaymentMethodChoice(paymentMethods.PayNow);
}
// Call APIs
@ -535,7 +535,9 @@ export default {
displayAmount: this.getDisplayAmountDue(),
payInAdvanceLineItems: '',
shouldBlockInteraction: false,
afterpayModalOpen: false
afterpayModalOpen: false,
iframeLoaded: false,
iframeResizerLoaded: false
};
},
computed: {
@ -776,6 +778,7 @@ export default {
} else {
iframe.contentWindow.postMessage('CreditCardChosen', '*');
}
this.iframeLoaded = true;
}
};
}
@ -788,6 +791,7 @@ export default {
}
if (event.data.includes('afterpayClosed')) {
this.allowIFrameResize(iframe);
if (this.isAfterPay) {
this.backButtonAction();
} else {
@ -810,7 +814,6 @@ export default {
useMainStore().savePaymentMethodChoice(paymentMethods.AFTERPAY);
}
if (event.data.includes('applepayOpened')) {
this.showIssLoadingModal(true);
useMainStore().savePaymentMethodChoice(paymentMethods.APPLEPAY);
}
if (event.data.includes('showLoaderForPaypal')) {
@ -855,17 +858,28 @@ export default {
},
submitPaymentAction() {
const iframe = this.$refs.paymentFrame;
if (iframe && iframe.contentWindow) {
if (iframe && iframe.contentWindow && this.iframeLoaded) {
iframe.contentWindow.postMessage('submitPayment', '*');
}
},
switchToAfterPay() {
const iframe = this.$refs.paymentFrame;
if (iframe && iframe.contentWindow) {
if (iframe && iframe.contentWindow && this.iframeLoaded) {
this.afterpayModalOpen = true;
this.lockIFrameSize(iframe);
iframe.contentWindow.postMessage('afterpay-switch', '*');
}
},
lockIFrameSize(iframe) {
if (iframe && iframe.iFrameResizer) {
iframe.iFrameResizer.removeListeners();
}
},
allowIFrameResize(iframe) {
if (iframe) {
iframeResize({ checkOrigin: false }, iframe);
}
},
showIssLoadingModal
}
};

View file

@ -105,7 +105,8 @@ const navigationScenarios = Object.freeze({
PAY_IN_ADVANCE_ERROR: 'PAY_IN_ADVANCE_ERROR',
PAY_IN_ADVANCE_CREDIT_CARD_ERROR: 'PAY_IN_ADVANCE_CREDIT_CARD_ERROR',
PAY_IN_ADVANCE_SUCCESS: 'PAY_IN_ADVANCE_SUCCESS',
EDIT_SERVICE_LOCATION: 'EDIT_SERVICE_LOCATION',
EDIT_SERVICE_LOCATION_INSHOP: 'EDIT_SERVICE_LOCATION_INSHOP',
EDIT_SERVICE_LOCATION_MOBILE: 'EDIT_SERVICE_LOCATION_MOBILE',
EDIT_SCHEDULE: 'EDIT_SCHEDULE',
EDIT_WIPERS: 'EDIT_WIPERS',

View file

@ -638,9 +638,13 @@ const routingTable = () => [
destinationIssPageValue: issPageValues.PAYMENT_PAGE
},
{
scenario: navigationScenarios.EDIT_SERVICE_LOCATION,
scenario: navigationScenarios.EDIT_SERVICE_LOCATION_INSHOP,
destinationIssPageValue: issPageValues.SCHEDULE_PAGE
},
{
scenario: navigationScenarios.EDIT_SERVICE_LOCATION_MOBILE,
destinationIssPageValue: issPageValues.CONTACT_DETAILS
},
{
scenario: navigationScenarios.EDIT_SCHEDULE,
destinationIssPageValue: issPageValues.SCHEDULE_PAGE