Merge branch 'develop' into feature/CSR-1392
This commit is contained in:
commit
dc4ec8cdd0
63 changed files with 9743 additions and 1181 deletions
|
|
@ -31,5 +31,5 @@ module.exports = {
|
|||
},
|
||||
},
|
||||
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit
|
||||
silent: true,
|
||||
//silent: true,
|
||||
};
|
||||
|
|
|
|||
336
public/css/hop-styling.css
Normal file
336
public/css/hop-styling.css
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
.btn-group-lg > .btn {
|
||||
padding: 10px 20px;
|
||||
font-size: 20px;
|
||||
line-height: 1.3;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.btn-group-sm > .btn {
|
||||
padding: 3px 6px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.btn-group-xs > .btn {
|
||||
padding: 1px 5px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.dl-horizontal dd:before,
|
||||
.dl-horizontal dd:after,
|
||||
.container:before,
|
||||
.container:after,
|
||||
.container-fluid:before,
|
||||
.container-fluid:after,
|
||||
.row:before,
|
||||
.row:after,
|
||||
.form-horizontal .form-group:before,
|
||||
.form-horizontal .form-group:after,
|
||||
.btn-toolbar:before,
|
||||
.btn-toolbar:after,
|
||||
.btn-group-vertical > .btn-group:before,
|
||||
.btn-group-vertical > .btn-group:after,
|
||||
.panel-body:before,
|
||||
.panel-body:after,
|
||||
.modal-footer:before,
|
||||
.modal-footer:after {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
.dl-horizontal dd:after,
|
||||
.container:after,
|
||||
.container-fluid:after,
|
||||
.row:after,
|
||||
.form-horizontal .form-group:after,
|
||||
.btn-toolbar:after,
|
||||
.btn-group-vertical > .btn-group:after,
|
||||
.panel-body:after,
|
||||
.modal-footer:after {
|
||||
clear: both;
|
||||
}
|
||||
[data-icon]:before,
|
||||
[data-icon-after]:after,
|
||||
.ico:before {
|
||||
speak: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-transform: none !important;
|
||||
font-weight: normal !important;
|
||||
font-style: normal !important;
|
||||
font-variant: normal !important;
|
||||
font-size: 20px !important;
|
||||
font-family: "safelite-ico" !important;
|
||||
line-height: 20px !important;
|
||||
}
|
||||
html {
|
||||
position: static !important;
|
||||
height: auto !important;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
background-color: #ffffff !important;
|
||||
}
|
||||
body {
|
||||
margin-bottom: 16px !important;
|
||||
}
|
||||
.headerlinecontainer {
|
||||
color: #000000;
|
||||
font-family: Roboto, Arial, "Helvetica Neue", Helvetica, Tahoma, Verdana, sans-serif;
|
||||
}
|
||||
.headerline1 {
|
||||
display: block;
|
||||
margin-bottom: 11.5px;
|
||||
font-weight: 300;
|
||||
font-size: 20px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.headerline2,
|
||||
.headerline3,
|
||||
.headerline4,
|
||||
.headerline5 {
|
||||
margin-bottom: 11.5px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.headerline3 {
|
||||
color: #0c7e47;
|
||||
}
|
||||
.headerline5 {
|
||||
display: block;
|
||||
padding: 11.5px 0;
|
||||
color: #167cac;
|
||||
font-style: italic;
|
||||
}
|
||||
.headerline5v2 {
|
||||
display: block;
|
||||
padding: 11.5px 0;
|
||||
color: #da291c;
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.form-group .control-label {
|
||||
display: block;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.form-group .form-control {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.form-group {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
@media (min-width: 800px) {
|
||||
.form-group {
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
#cardExpirationContainer .card_expirationMonth,
|
||||
#cardExpirationContainer .card_expirationYear {
|
||||
position: relative;
|
||||
width: 49%;
|
||||
}
|
||||
#cardExpirationContainer .card_expirationMonth {
|
||||
float: left;
|
||||
}
|
||||
#cardExpirationContainer .card_expirationYear {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
.totalContainer {
|
||||
margin: 16px 0;
|
||||
padding: 8px;
|
||||
border-radius: 5px;
|
||||
background-color: #0c7e47;
|
||||
color: #ffffff;
|
||||
font-size: 16px;
|
||||
}
|
||||
.totalContainer em {
|
||||
font-weight: 500;
|
||||
}
|
||||
.totalContainer .strikeThrough {
|
||||
padding-left: 8px;
|
||||
text-decoration: line-through;
|
||||
font-size: 14px;
|
||||
}
|
||||
.callerDisplayTextContainer {
|
||||
margin: 16px 0;
|
||||
font-style: italic;
|
||||
}
|
||||
.disclaimerContainer {
|
||||
margin: 16px 0;
|
||||
padding: 8px;
|
||||
border-radius: 5px;
|
||||
background-color: #f0f1f2;
|
||||
}
|
||||
.disclaimerContainerv2 {
|
||||
margin: 16px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.buttonContainer #sghopcancelbutton {
|
||||
float: left;
|
||||
}
|
||||
.buttonContainer #sghopsubmitbutton {
|
||||
float: right;
|
||||
}
|
||||
.buttonContainer #sghopsubmitbutton.creditCardSpecific {
|
||||
float: right;
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
.buttonContainer #sghopsubmitbutton.creditCardSpecific {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.buttonContainer #selectPaymentTypeButton {
|
||||
float: right;
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
.buttonContainer #selectPaymentTypeButton {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.globalMessage .message {
|
||||
padding-left: 0;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.globalMessage .message strong {
|
||||
color: #da291c;
|
||||
}
|
||||
.italicSmall {
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-size: 15px;
|
||||
}
|
||||
#pageLoadingModal {
|
||||
background-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==");
|
||||
}
|
||||
#pageLoadingModal .modal-content {
|
||||
margin-top: 100px;
|
||||
}
|
||||
#paypalParentDiv {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
#creditCardParentDiv {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.radio .list-group.paymentSelection,
|
||||
.radio.list-group.paymentSelection {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@media (max-width: 33.9em) {
|
||||
.radio .list-group.paymentSelection label,
|
||||
.radio.list-group.paymentSelection label,
|
||||
.radio .list-group.paymentSelection a,
|
||||
.radio.list-group.paymentSelection a {
|
||||
max-height: 52px;
|
||||
}
|
||||
}
|
||||
#creditCardParentLink {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@media (max-width: 33.9em) {
|
||||
#creditCardParentLink {
|
||||
min-height: 57px;
|
||||
}
|
||||
}
|
||||
#creditCardImages,
|
||||
#paypalImageSpan {
|
||||
padding-right: 0;
|
||||
}
|
||||
@media (max-width: 33.9em) {
|
||||
.paymentOptionText {
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
.extraPaddingTop {
|
||||
padding-top: 20px;
|
||||
}
|
||||
.extraPaddingBottom {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
@media (min-width: 33.9em) {
|
||||
#paypalImage {
|
||||
max-width: 22%;
|
||||
}
|
||||
}
|
||||
@media (max-width: 33.9em) {
|
||||
#paypalImage {
|
||||
max-width: 55px;
|
||||
}
|
||||
}
|
||||
label.centerme {
|
||||
position: inherit;
|
||||
display: table-cell;
|
||||
width: 9999px !important;
|
||||
/*This is kind of a hack*/
|
||||
vertical-align: middle;
|
||||
}
|
||||
input[name="rdioCreditCard"] {
|
||||
display: block;
|
||||
}
|
||||
@media (min-width: 33.9em) {
|
||||
input[name="rdioCreditCard"] {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 33.9em) {
|
||||
span.paymentOptionText {
|
||||
margin-top: 6px;
|
||||
}
|
||||
}
|
||||
.headerline1.safelitehop {
|
||||
margin-top: 20px;
|
||||
}
|
||||
@media (max-width: 499px) {
|
||||
#buttonContainer {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.has-success .control-label {
|
||||
color: #4d4e53;
|
||||
}
|
||||
#thm_fp {
|
||||
display: block;
|
||||
}
|
||||
#paypalParentLink label,
|
||||
#creditCardParentLink label {
|
||||
padding-top: 9px;
|
||||
padding-bottom: 9px;
|
||||
}
|
||||
#paypalParentLink label .paymentOptionText,
|
||||
#creditCardParentLink label .paymentOptionText {
|
||||
padding-left: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
#paypalParentLink {
|
||||
margin-top: 12px;
|
||||
}
|
||||
html #creditCardParentLink.list-group-item {
|
||||
border-radius: 48px;
|
||||
text-align: center;
|
||||
}
|
||||
.optional-label span {
|
||||
display: none;
|
||||
}
|
||||
.optional-label::after {
|
||||
content: " (optional)";
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-size: 15px;
|
||||
}
|
||||
.creditCardSpecific {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
.buy-backdrop.afterpay-checkout-wrapper {
|
||||
-webkit-box-align: start;
|
||||
-ms-flex-align: start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
7763
public/css/site-2020Styling.css
Normal file
7763
public/css/site-2020Styling.css
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -11,16 +11,20 @@ const applicationConfig = {
|
|||
SITE_ENTRY_TRIGGER_VALUE: "FixMyGlass",
|
||||
CASH_PARENT_ACCOUNT_NUMBER: 167132,
|
||||
MY_ACCOUNT: process.env.VUE_APP_MY_ACCOUNT,
|
||||
PAYPAL_SUCCESS_URL:
|
||||
PIA_RESPONSE_URL:
|
||||
location.protocol +
|
||||
"//" +
|
||||
location.host +
|
||||
"/fmg/?fmgPage=payment-pia-return&src=concept-funnel",
|
||||
PAYPAL_CANCEL_URL:
|
||||
PIA_CANCEL_URL:
|
||||
location.protocol +
|
||||
"//" +
|
||||
location.host +
|
||||
"/fmg/?fmgPage=payment-method&src=concept-funnel",
|
||||
GOOGLE_CALENDAR: "https://www.google.com/calendar/render?action=TEMPLATE",
|
||||
YAHOO_CALENDAR: "https://calendar.yahoo.com/?v=60",
|
||||
OUTLOOK_CALENDAR:
|
||||
"https://outlook.office.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent",
|
||||
};
|
||||
|
||||
export { applicationConfig };
|
||||
|
|
|
|||
|
|
@ -5,9 +5,3 @@ export const paymentMethods = {
|
|||
PAYPAL: "Paypal",
|
||||
AFTERPAY: "Afterpay",
|
||||
};
|
||||
|
||||
export const paymentTimes = {
|
||||
NONE: null,
|
||||
ADVANCE: "Advance",
|
||||
LATER: "Later",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
// Note: querystring-helper will change all keys to lowercase so make sure they are here as well
|
||||
const queryStrings = {
|
||||
FMG_PAGE: "fmgPage",
|
||||
START_TYPE: "start_type",
|
||||
|
|
@ -6,6 +7,18 @@ const queryStrings = {
|
|||
ERROR: "error",
|
||||
TOKEN: "token",
|
||||
PAYERID: "payerid",
|
||||
SUBSCRIPTIONID: "subscriptionid",
|
||||
REFERRAL_SEQ_NUM: "referralseqnum",
|
||||
CARD_EXPIRATION_MONTH: "card_expirationmonth",
|
||||
CARD_EXPIRATION_YEAR: "card_expirationyear",
|
||||
CARD_TYPE: "sgcardtype",
|
||||
BILL_TO_POSTAL_CODE: "billto_postalcode",
|
||||
BILL_TO_FIRST_NAME: "billto_firstname",
|
||||
BILL_TO_LAST_NAME: "billto_lastname",
|
||||
REFERENCE_NUMBER: "req_reference_number",
|
||||
AUTH_CODE: "auth_code",
|
||||
TRANSACTION_ID: "transaction_id",
|
||||
TRANS_REFERENCE_NUMBER: "auth_trans_ref_no",
|
||||
};
|
||||
|
||||
export { queryStrings };
|
||||
|
|
|
|||
6
src/constants/service-type.js
Normal file
6
src/constants/service-type.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
const serviceType = {
|
||||
REPLACEMENT: "replacement",
|
||||
REPAIR: "repair",
|
||||
REPLACEMENT_AND_RECALIBRATION: "replacement and recalibration",
|
||||
};
|
||||
export { serviceType };
|
||||
|
|
@ -80,7 +80,7 @@ const storeActions = {
|
|||
SAVE_MOLDING_QUESTION_ANSWERS: "saveMoldingQuestionAnswers",
|
||||
SAVE_CAPABILITY_QUESTION_ANSWERS: "saveCapabilityQuestionAnswers",
|
||||
SAVE_PAYMENT_TYPE: "savePaymentType",
|
||||
SAVE_PAY_NOW_TYPE: "savePayNowType",
|
||||
SAVE_PAYMENT_METHOD_CHOICE: "savePaymentMethodChoice",
|
||||
SAVE_PARENT_ACCOUNT_NUMBER: "saveParentAccountNumber",
|
||||
SAVE_SUPPORTING_ITEMS: "saveSupportingItems",
|
||||
SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING:
|
||||
|
|
@ -92,6 +92,8 @@ const storeActions = {
|
|||
SAVE_WORK_ORDER_FLAG: "saveWorkOrderFlag",
|
||||
SAVE_PIA_ERROR_CODE: "savePiaErrorCode",
|
||||
SAVE_PIA_WORK_ORDER: "savePiaWorkOrder",
|
||||
SAVE_CCTOKEN: "saveCCToken",
|
||||
SAVE_PAYPAL_TOKEN: "savePaypalToken",
|
||||
};
|
||||
|
||||
export { storeActions };
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
const storeMutations = {
|
||||
// PAYMENT MUTATIONS
|
||||
UPDATE_WORK_ORDER_FLAG: "updateWorkOrderFlag",
|
||||
UPDATE_PIA_ERROR_CODE: "updatePiaErrorCode",
|
||||
UPDATE_PIA_WORK_ORDER: "updatePiaWorkOrder",
|
||||
UPDATE_CCTOKEN: "updateCCToken",
|
||||
UPDATE_PAYPAL_TOKEN: "updatePaypalToken",
|
||||
|
||||
// VEHICLE MUTATIONS
|
||||
UPDATE_YEAR: "updateYear",
|
||||
|
|
@ -50,7 +53,8 @@ const storeMutations = {
|
|||
UPDATE_IS_INSURANCE: "updateIsInsurance",
|
||||
UPDATE_SAVED_SESSION_ID: "updateSavedSessionId",
|
||||
UPDATE_CRM_CUSTOMER_ID: "updateCrmCustomerId",
|
||||
UPDATE_PAY_NOW_TYPE: "updatePayNowType",
|
||||
UPDATE_IS_PIA: "updateIsPia",
|
||||
UPDATE_PIA_TYPE: "updatePiaType",
|
||||
WORK_ORDER_NUMBER: "updateWorkOrderNumber",
|
||||
|
||||
// EVENT BUS MUTATIONS
|
||||
|
|
@ -68,6 +72,7 @@ const storeMutations = {
|
|||
RESET_SERVICE_LOCATION_PROVIDER: "resetServiceLocationProvider",
|
||||
RESET_SERVICE_LOCATION_MOBILE_ADDRESS: "resetServiceLocationMobileAddress",
|
||||
RESET_SCHEDULE: "resetSchedule",
|
||||
RESET_PAYMENT_METHOD_CHOICE: "resetPaymentMethodChoice",
|
||||
|
||||
// OTHER MUTATIONS
|
||||
UPDATE_PAGE_DATA: "updatePageData",
|
||||
|
|
|
|||
|
|
@ -72,7 +72,10 @@ https://safelite.atlassian.net/wiki/spaces/DC/pages/76644418/Button+Question+Com
|
|||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="row form-test-error">
|
||||
<div
|
||||
id="form-test-error"
|
||||
class="row form-test-error"
|
||||
:class="isErrorCentered ? 'text-center' : ''">
|
||||
<error-message
|
||||
role="comment"
|
||||
aria-atomic="true"
|
||||
|
|
@ -227,8 +230,18 @@ export default {
|
|||
classes += " two-list-card-width";
|
||||
}
|
||||
|
||||
if (this.buttonTypeString == "listCard" && this.buttonsInfo.length === 1) {
|
||||
classes += " one-list-card-width";
|
||||
}
|
||||
|
||||
return classes;
|
||||
},
|
||||
isErrorCentered() {
|
||||
if (this.buttonTypeString == "listCard" && this.buttonsInfo.length <= 2) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
buttonsInfo() {
|
||||
return (Array.isArray(this.answers) ? this.answers : [])?.map((answer) => ({
|
||||
buttonLabel: answer.buttonLabel ?? answer.Text ?? answer,
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
<template>
|
||||
<!-- Checkbox groups MUST be wrapped in a <fieldset> and <legend> tag -->
|
||||
<div class="form-check ui-checkbox" :class="[hasError ? 'has-error' : '']">
|
||||
<input
|
||||
v-model="value"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
aria-checked="false"
|
||||
:name="checkboxName"
|
||||
:id="buttonID"
|
||||
:tabindex="tabIndex"
|
||||
:aria-required="isRequired" />
|
||||
<div
|
||||
class="form-check ui-checkbox d-flex align-items-center"
|
||||
:class="[hasError ? 'has-error' : '']">
|
||||
<label class="d-flex align-items-start" :for="buttonID">
|
||||
<input
|
||||
v-model="value"
|
||||
class="form-check-input me-2"
|
||||
type="checkbox"
|
||||
aria-checked="false"
|
||||
:name="checkboxName"
|
||||
:id="buttonID"
|
||||
:tabindex="tabIndex"
|
||||
:aria-required="isRequired" />
|
||||
<p v-html="checkboxLabelCopy" class="m-0"></p>
|
||||
<span v-if="screenReaderOnlyText" class="sr-only">{{ screenReaderOnlyText }}</span>
|
||||
</label>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
@mousedown="closeModal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body ps-5 pe-5 pb-4 pt-0">
|
||||
<div class="modal-body ps-5 pe-5 pb-4 pt-1">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="modal-footer px-5 py-4">
|
||||
|
|
@ -179,7 +179,7 @@ export default {
|
|||
}
|
||||
}
|
||||
@include media-breakpoint-up(md) {
|
||||
overflow: scroll;
|
||||
overflow: auto;
|
||||
flex: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -198,10 +198,6 @@ export default {
|
|||
button {
|
||||
margin: 0;
|
||||
}
|
||||
@include media-breakpoint-up(md) {
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.show .modal-dialog {
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
</div>
|
||||
<div v-show="errorMessage" class="row form-test-error">
|
||||
<span
|
||||
class="d-inline-flex small my-1"
|
||||
class="d-inline-flex small mt-1"
|
||||
aria-atomic="true"
|
||||
aria-live="polite"
|
||||
:class="[centerErrorMessage ? 'center-error-message' : '']"
|
||||
|
|
|
|||
|
|
@ -44,13 +44,13 @@ export default {
|
|||
showAddRainDefense() {
|
||||
// if not found in the cart
|
||||
return !this.cartItems.some((vap) => {
|
||||
return vap.partType === "RAIN DEFENSE";
|
||||
return vap?.partType === "RAIN DEFENSE";
|
||||
});
|
||||
},
|
||||
showAddWipers() {
|
||||
// if not found in the cart
|
||||
return !this.cartItems.some((vap) => {
|
||||
return vap.partType.includes(" WIPER");
|
||||
return vap?.partType?.includes(" WIPER");
|
||||
});
|
||||
},
|
||||
rainDefensePrice() {
|
||||
|
|
@ -59,7 +59,7 @@ export default {
|
|||
return item.partType === "RAIN DEFENSE";
|
||||
});
|
||||
|
||||
const price = lineItem.laborAmount + lineItem.sellingPrice + lineItem.kitPrice;
|
||||
const price = lineItem?.laborAmount + lineItem?.sellingPrice + lineItem?.kitPrice;
|
||||
return parseFloat(price).toFixed(2);
|
||||
},
|
||||
wipersPrice() {
|
||||
|
|
@ -68,7 +68,7 @@ export default {
|
|||
return item.partType.includes(" WIPER");
|
||||
});
|
||||
lineItems?.forEach((item) => {
|
||||
price += item.laborAmount + item.sellingPrice + item.kitPrice;
|
||||
price += item?.laborAmount + item?.sellingPrice + item?.kitPrice;
|
||||
});
|
||||
return parseFloat(price).toFixed(2);
|
||||
},
|
||||
|
|
@ -87,12 +87,13 @@ export default {
|
|||
};
|
||||
|
||||
if (this.showAddWipers) {
|
||||
const answer = getAnswer("ADD WIPER BLADES", this.answersCmsData);
|
||||
const answer = getAnswer("WipersModal", this.answersCmsData);
|
||||
answer.SubText = "+$" + this.wipersPrice;
|
||||
answers.push(answer);
|
||||
}
|
||||
|
||||
if (this.showAddRainDefense) {
|
||||
const answer = getAnswer("ADD RAIN DEFENSE", this.answersCmsData);
|
||||
const answer = getAnswer("RainDefenseModal", this.answersCmsData);
|
||||
answer.SubText = "+$" + this.rainDefensePrice;
|
||||
answers.push(answer);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,14 @@
|
|||
@click="toggleClass()">
|
||||
<div class="col d-flex justify-content-between">
|
||||
<span class="label">Amount Due</span
|
||||
><span class="label amount-due">{{ amountDue }}</span>
|
||||
><span class="label amount-due">{{ currencyFormatter.format(amountDue) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="price-table">
|
||||
<div class="service-type" :class="{ packageWithVAPS: packageWithVAPS }">
|
||||
<textBlock :cmsWidgetName="packageNameWidget" /><span>{{ packagePrice }}</span>
|
||||
<textBlock :cmsWidgetName="packageNameWidget" /><span>{{
|
||||
currencyFormatter.format(packagePrice)
|
||||
}}</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="(lineItem, i) in displayedPackageLineItems"
|
||||
|
|
@ -22,7 +24,7 @@
|
|||
<textLink
|
||||
ref="removeLink"
|
||||
linkType="text"
|
||||
text="showMoreShopsLinkText"
|
||||
text="removeLinkText"
|
||||
href="#!"
|
||||
@click-event="removeItem(lineItem)" />
|
||||
</div>
|
||||
|
|
@ -35,7 +37,7 @@
|
|||
<textLink
|
||||
ref="removeLink"
|
||||
linkType="text"
|
||||
text="showMoreShopsLinkText"
|
||||
text="removeLinkText"
|
||||
href="#!"
|
||||
@click-event="removeItem(lineItem)" />
|
||||
<span>{{ lineItem.price }}</span>
|
||||
|
|
@ -45,9 +47,15 @@
|
|||
><span>{{ recycleFee }}</span>
|
||||
</div>
|
||||
<!-- Sub total, sales tax, total columns -->
|
||||
<div class="sub-total"><span>Subtotal</span><span>$320</span></div>
|
||||
<div class="sales-tax"><span>Sales tax</span><span>$1</span></div>
|
||||
<div class="amount-due"><span>Amount due</span><span>$321</span></div>
|
||||
<div class="sub-total">
|
||||
<span>Subtotal</span><span>{{ currencyFormatter.format(subTotal) }}</span>
|
||||
</div>
|
||||
<div class="sales-tax">
|
||||
<span>Sales tax</span><span>{{ currencyFormatter.format(salesTax) }}</span>
|
||||
</div>
|
||||
<div class="amount-due">
|
||||
<span>Amount due</span><span>{{ currencyFormatter.format(amountDue) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -76,28 +84,16 @@ export default {
|
|||
return {
|
||||
isActive: false,
|
||||
packageWithVAPS: false,
|
||||
amountDue: this.getAmountDue(),
|
||||
currencyFormatter: new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
}),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
toggleClass: function (event) {
|
||||
this.isActive = !this.isActive;
|
||||
},
|
||||
getPackagePriceString(packageName) {
|
||||
const formattedPriceFloat = parseFloat(this.getPackagePrice(packageName)).toFixed(2);
|
||||
return (this.isInsuranceSelected ? "As little as $" : "$") + formattedPriceFloat;
|
||||
},
|
||||
getPackagePrice(packageName) {
|
||||
let priceFloat = this.isInsuranceSelected
|
||||
? 0
|
||||
: baseMixin.methods.getTierOnePackagePrice(
|
||||
baseMixin.methods.filterOutFees(this.availableLineItems)
|
||||
);
|
||||
|
||||
priceFloat += this.getVapsPrice(packageName);
|
||||
|
||||
return priceFloat;
|
||||
},
|
||||
getVapsPrice(packageName) {
|
||||
const vapsItems = this.getVapsLineItemsForSelectedPackage(packageName);
|
||||
|
||||
|
|
@ -157,19 +153,24 @@ export default {
|
|||
this.isRepair,
|
||||
this.vaps
|
||||
);
|
||||
|
||||
return tier;
|
||||
},
|
||||
packagePrice() {
|
||||
let packagePriceString;
|
||||
if (this.packageLevel.length > 0) {
|
||||
packagePriceString = this.getPackagePriceString(this.packageLevel);
|
||||
}
|
||||
return packagePriceString;
|
||||
// return (packagePriceString.length > 0) ? packagePriceString : "Test String";
|
||||
let packagePrice = this.isInsuranceSelected
|
||||
? 0
|
||||
: baseMixin.methods.getTierOnePackagePrice(
|
||||
baseMixin.methods.filterOutFees(this.availableLineItems)
|
||||
);
|
||||
|
||||
packagePrice += this.getVapsPrice(this.packageLevel);
|
||||
|
||||
return packagePrice;
|
||||
},
|
||||
displayedPackageLineItems() {
|
||||
const displayedPackageLineItems = [];
|
||||
const packageLineItems = this.getVapsLineItemsForSelectedPackage(this.packageLevel);
|
||||
|
||||
// TODO Determine if we could get the same list from this.lineItems?
|
||||
packageLineItems?.forEach((item) => {
|
||||
let price = item.sellingPrice + item.kitPrice + item.laborAmount;
|
||||
|
|
@ -181,6 +182,7 @@ export default {
|
|||
};
|
||||
displayedPackageLineItems.push(packageLineItem);
|
||||
});
|
||||
|
||||
return displayedPackageLineItems;
|
||||
},
|
||||
displayedNonPackageLineItems() {
|
||||
|
|
@ -229,6 +231,9 @@ export default {
|
|||
const supportingItems = this.lineItems?.supportingItems;
|
||||
return supportingItems?.length > 0 ? supportingItems : [];
|
||||
},
|
||||
removeLinkText() {
|
||||
return "Remove"; // TODO: get from CMS
|
||||
},
|
||||
recycleLabel() {
|
||||
return "Recycling";
|
||||
},
|
||||
|
|
@ -244,6 +249,29 @@ export default {
|
|||
// }
|
||||
return true;
|
||||
},
|
||||
subTotal() {
|
||||
let subTotal = 0;
|
||||
|
||||
this.lineItems.forEach((lineItem) => {
|
||||
subTotal +=
|
||||
(lineItem.kitPrice ?? 0) +
|
||||
(lineItem.laborAmount ?? 0) +
|
||||
(lineItem.sellingPrice ?? 0);
|
||||
});
|
||||
|
||||
return subTotal;
|
||||
},
|
||||
salesTax() {
|
||||
let salesTax = 0;
|
||||
this.lineItems.forEach((lineItem) => {
|
||||
salesTax += lineItem.salesTax;
|
||||
});
|
||||
|
||||
return salesTax;
|
||||
},
|
||||
amountDue() {
|
||||
return this.subTotal + this.salesTax;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
textBlock,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
class="my-4 caption modal-sub-body"
|
||||
v-if="ModalSubBodyText"
|
||||
v-html="ModalSubBodyText"></p>
|
||||
<slot></slot>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
|
|
@ -22,6 +23,10 @@ export default {
|
|||
name: "content-group-modal",
|
||||
props: {
|
||||
cmsWidgetName: String,
|
||||
footerButtonActionName: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
ModalName() {
|
||||
|
|
@ -50,10 +55,16 @@ export default {
|
|||
openModal() {
|
||||
this.$refs[this.ModalName].openModal();
|
||||
},
|
||||
|
||||
footerButtonClick() {
|
||||
closeModal() {
|
||||
this.$refs[this.ModalName].closeModal();
|
||||
},
|
||||
footerButtonClick() {
|
||||
if (this.footerButtonActionName) {
|
||||
this.$emit(this.footerButtonActionName, this.ModalName);
|
||||
} else {
|
||||
this.closeModal();
|
||||
}
|
||||
},
|
||||
},
|
||||
components: {
|
||||
modal,
|
||||
|
|
|
|||
|
|
@ -7,14 +7,17 @@
|
|||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<alert
|
||||
ref="alertFewMoreQuestions"
|
||||
cmsWidgetName="alertWidget"
|
||||
|
|
@ -25,7 +28,7 @@
|
|||
v-bind:isDismissible="false" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<div v-for="(questionsDatum, i) in questionsData" :key="questionsDatum.key">
|
||||
<questionChain
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
@click-event="buttonClick"
|
||||
data-bs-target="#footerModal"
|
||||
data-test-id="nav-bar-main-button"
|
||||
data-bs-dismiss="modal" />
|
||||
data-bs-dismiss="modal"
|
||||
v-if="!isSubmitHidden" />
|
||||
</div>
|
||||
<div v-if="!isBackButtonHidden" class="col-auto link-col py-1 text-break">
|
||||
<textLink
|
||||
|
|
@ -44,6 +45,7 @@ export default {
|
|||
isBackButtonHidden: { type: Boolean, default: false },
|
||||
cmsWidgetName: String,
|
||||
buttonSize: { type: Boolean, default: false },
|
||||
isSubmitHidden: { type: Boolean, default: false },
|
||||
},
|
||||
components: {
|
||||
textLink,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="vehicle_banner mb-3 text-center">
|
||||
<div class="vehicle_banner mb-4 text-center">
|
||||
<img class="vehicle-image img-fluid" :src="vehicleImageToDisplay" alt="" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -7,22 +7,6 @@ export function getDateDifferenceInDays(startDate, endDate) {
|
|||
// Convert milliseconds to days and return the result
|
||||
return difference / (1000 * 3600 * 24);
|
||||
}
|
||||
export function getFullDayName(date) {
|
||||
// Use a ternary operator to check if the input is a valid date object
|
||||
return date instanceof Date
|
||||
? // Use the built-in method toLocaleDateString() to get the full day name in the current locale
|
||||
date.toLocaleDateString(undefined, { weekday: "long" })
|
||||
: // Return undefined if the input is not a valid date object
|
||||
undefined;
|
||||
}
|
||||
export function getFullMonthName(date) {
|
||||
// Use a ternary operator to check if the input is a valid date object
|
||||
return date instanceof Date
|
||||
? // Use the built-in method toLocaleDateString() to get the full month name in the current locale
|
||||
date.toLocaleDateString(undefined, { month: "long" })
|
||||
: // Return undefined if the input is not a valid date object
|
||||
undefined;
|
||||
}
|
||||
export function get12HourTimeFormat(time) {
|
||||
// Check correct time format and split into components
|
||||
time = time.toString().match(/^([01]\d|2[0-3])(:)([0-5]\d)?$/) || [time];
|
||||
|
|
@ -112,7 +96,7 @@ export function shortTimeString(date) {
|
|||
// Use a ternary operator to check if the input is a valid date object
|
||||
return date instanceof Date
|
||||
? // Use the built-in method toLocaleTimeString() to get the short time string in the current locale
|
||||
date.toLocaleTimeString(undefined, { hour: "numeric", minute: "numeric", hour12: true })
|
||||
date.toLocaleTimeString("en-us", { hour: "numeric", minute: "numeric", hour12: true })
|
||||
: // Return undefined if the input is not a valid date object
|
||||
undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import {
|
||||
getFullDayName,
|
||||
getFullMonthName,
|
||||
get12HourTimeFormat,
|
||||
get12HourTimeMobileFormat,
|
||||
getDateFormat,
|
||||
|
|
@ -52,33 +50,17 @@ describe("date-helper.js", () => {
|
|||
}
|
||||
});
|
||||
|
||||
// it("getFullMonthName should return full month format.", () => {
|
||||
// // Arrange / Act
|
||||
// const date = new Date("2023-10-01");
|
||||
// const monthName = getFullMonthName(date);
|
||||
it("getDateFormat should return date in the given format.", () => {
|
||||
// Arrange / Act
|
||||
const dateString = "2023-10-01";
|
||||
const dateParts = dateString.split("-");
|
||||
const date = new Date(dateParts[0], parseInt(dateParts[1]) - 1, dateParts[2]);
|
||||
const format = "yyyy-MM-dd";
|
||||
const formattedDate = getDateFormat(date, format);
|
||||
|
||||
// // Assert
|
||||
// expect(monthName).toEqual("October");
|
||||
// });
|
||||
|
||||
// it("getFullDayName should return full Day Name format.", () => {
|
||||
// // Arrange / Act
|
||||
// const date = new Date("2023-10-01");
|
||||
// const dayName = getFullDayName(date);
|
||||
|
||||
// // Assert
|
||||
// expect(dayName).toEqual("Sunday");
|
||||
// });
|
||||
|
||||
// it("getDateFormat should return date in the given format.", () => {
|
||||
// // Arrange / Act
|
||||
// const date = new Date("2023-10-01");
|
||||
// const format = "yyyy-MM-dd";
|
||||
// const formattedDate = getDateFormat(date, format);
|
||||
|
||||
// // Assert
|
||||
// expect(formattedDate).toEqual("2023-10-01");
|
||||
// });
|
||||
// Assert
|
||||
expect(formattedDate).toEqual("2023-10-01");
|
||||
});
|
||||
|
||||
it("should return the correct difference in days", function () {
|
||||
// Define some sample dates and their expected differences
|
||||
|
|
|
|||
|
|
@ -39,12 +39,14 @@ export function getAvailablePackages(glassToReplace, availableLineItems, isRepai
|
|||
isRepair,
|
||||
packageNames.TIER_ONE
|
||||
);
|
||||
|
||||
let tierTwoVaps = getPackageContents(
|
||||
glassToReplace,
|
||||
availableLineItems,
|
||||
isRepair,
|
||||
packageNames.TIER_TWO
|
||||
);
|
||||
|
||||
let tierThreeVaps = getPackageContents(
|
||||
glassToReplace,
|
||||
availableLineItems,
|
||||
|
|
@ -87,9 +89,11 @@ export function getPackageContents(glassToReplace, availableLineItems, isRepair,
|
|||
if (shouldFrontWipersBeAvailable(glassToReplace, availableLineItems, isRepair, targetTier)) {
|
||||
vaps.push(partTypeStrings.FRONT_WIPER);
|
||||
}
|
||||
|
||||
if (shouldRearWipersBeAvailable(glassToReplace, availableLineItems, isRepair, targetTier)) {
|
||||
vaps.push(partTypeStrings.REAR_WIPER);
|
||||
}
|
||||
|
||||
if (shouldRainDefenseBeAvailable(glassToReplace, availableLineItems, isRepair, targetTier)) {
|
||||
vaps.push(partTypeStrings.RAIN_DEFENSE);
|
||||
}
|
||||
|
|
@ -107,6 +111,7 @@ export function shouldFrontWipersBeAvailable(
|
|||
partTypeStrings.FRONT_WIPER,
|
||||
availableLineItems
|
||||
);
|
||||
|
||||
const isFrontWindshieldTask =
|
||||
isRepair || containsGlassPieceWithLocation(glassLocations.WINDSHIELD, glassToReplace);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,250 +0,0 @@
|
|||
// Components
|
||||
import addToCalendar from "@/layouts/add-to-calendar/add-to-calendar.vue";
|
||||
import { calendarOptions } from "@/constants/calendar-options";
|
||||
import { AppointmentTypeStrings, RouteCodeFlags } from "@/constants/schedule-constants";
|
||||
|
||||
// Supporting Files
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import store from "@/store";
|
||||
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
jest.clearAllMocks();
|
||||
store.getters = {
|
||||
order: {
|
||||
schedule: {
|
||||
date: "2019-01-01",
|
||||
startTime: "09:00",
|
||||
endTime: "10:00",
|
||||
routeCode: "000",
|
||||
},
|
||||
lineItems: {
|
||||
glassParts: [
|
||||
{
|
||||
partNumber: "ABC123",
|
||||
},
|
||||
],
|
||||
supportingItems: [],
|
||||
},
|
||||
serviceLocation: {
|
||||
address: "",
|
||||
address2: null,
|
||||
city: "",
|
||||
state: "AZ",
|
||||
appointmentType: "Inshop",
|
||||
zipCode: "12345",
|
||||
zipCodeCtu: "01234",
|
||||
provider: {
|
||||
providerNumber: "123",
|
||||
address: {
|
||||
streetAddress: "test1",
|
||||
city: "test",
|
||||
state: "AZ",
|
||||
zipCode: "12345",
|
||||
zipCodeCtu: "01234",
|
||||
},
|
||||
},
|
||||
},
|
||||
damage: {
|
||||
isRepair: false,
|
||||
},
|
||||
referralNumber: "1234567",
|
||||
},
|
||||
payment: {
|
||||
isInsurance: true,
|
||||
},
|
||||
lineItems: {
|
||||
glassParts: [],
|
||||
supportingItems: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
afterEach(() => {
|
||||
store.getters = {};
|
||||
jest.restoreAllMocks();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("Add-to-calendar methods...", () => {
|
||||
test("Add-to-calendar should trigger openModal method", () => {
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
wrapper.vm.openCalendarModal();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.$refs.calendarModalQuestion.openModal).toBeCalled();
|
||||
});
|
||||
test("serviceType should return 'replacement and recalibration' when isRepair and funnelHasRecalibrationPart true.", () => {
|
||||
//Arrange
|
||||
store.getters.order.damage.isRepair = true;
|
||||
store.getters.funnelHasRecalibrationPart = true;
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
const testValue = wrapper.vm.serviceType();
|
||||
|
||||
//Assert
|
||||
expect(testValue).toEqual("replacement and recalibration");
|
||||
});
|
||||
test("serviceType should return 'replacement' when isRepair is true and funnelHasRecalibrationPart is false.", () => {
|
||||
//Arrange
|
||||
store.getters.order.damage.isRepair = true;
|
||||
store.getters.funnelHasRecalibrationPart = false;
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
const testValue = wrapper.vm.serviceType();
|
||||
|
||||
//Assert
|
||||
expect(testValue).toEqual("replacement");
|
||||
});
|
||||
test("serviceType should return 'repair' when isRepair is false.", () => {
|
||||
//Arrange
|
||||
store.getters.order.damage.isRepair = false;
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
const testValue = wrapper.vm.serviceType();
|
||||
|
||||
//Assert
|
||||
expect(testValue).toEqual("repair");
|
||||
});
|
||||
test("getCalendarData should return expected model value.", () => {
|
||||
//Arrange
|
||||
const type = calendarOptions.OUTLOOKCOM;
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
const testValue = wrapper.vm.getCalendarData(type);
|
||||
|
||||
//Assert
|
||||
expect(testValue.name).toEqual(calendarOptions.OUTLOOKCOM);
|
||||
});
|
||||
test("getappointmentData should return expected model value for appointmentType mobile.", () => {
|
||||
//Arrange
|
||||
store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.MOBILE;
|
||||
store.getters.order.damage.isRepair = true;
|
||||
store.getters.funnelHasRecalibrationPart = true;
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
const testValue = wrapper.vm.getappointmentData();
|
||||
|
||||
//Assert
|
||||
expect(testValue.Subject).toEqual("Safelite replacement and recalibration appointment");
|
||||
});
|
||||
test("getappointmentData should return expected model value for appointmentType DROP_OFF and OVERNIGHT_DROP_OFF.", () => {
|
||||
//Arrange
|
||||
store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.DROP_OFF;
|
||||
store.getters.order.schedule.routeCode = RouteCodeFlags.OVERNIGHT_DROP_OFF;
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
const testValue = wrapper.vm.getappointmentData();
|
||||
|
||||
//Assert
|
||||
expect(testValue.Subject).toEqual("Drop off your vehicle by 5:30 PM");
|
||||
});
|
||||
test("getappointmentData should return expected model value for appointmentType DROP_OFF and ALL_DAY_DROP_OFF and isSameDayDropOff.", () => {
|
||||
//Arrange
|
||||
store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.DROP_OFF;
|
||||
store.getters.order.schedule.routeCode = RouteCodeFlags.ALL_DAY_DROP_OFF;
|
||||
store.getters.order.schedule.date = new Date().toISOString().split("T")[0];
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
const testValue = wrapper.vm.getappointmentData();
|
||||
|
||||
//Assert
|
||||
expect(testValue.Subject).toEqual("Drop off your vehicle in the next 30 - 60 minutes");
|
||||
});
|
||||
test("getappointmentData should return expected model value for appointmentType DROP_OFF and ALL_DAY_DROP_OFF.", () => {
|
||||
//Arrange
|
||||
store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.DROP_OFF;
|
||||
store.getters.order.schedule.routeCode = RouteCodeFlags.ALL_DAY_DROP_OFF;
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
const testValue = wrapper.vm.getappointmentData();
|
||||
|
||||
//Assert
|
||||
expect(testValue.Subject).toEqual("Drop off your vehicle between 9:00 AM - 11:00 AM");
|
||||
});
|
||||
test("getappointmentData should return expected model value for appointmentType DROP_OFF.", () => {
|
||||
//Arrange
|
||||
store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.DROP_OFF;
|
||||
store.getters.order.damage.isRepair = false;
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
const testValue = wrapper.vm.getappointmentData();
|
||||
|
||||
//Assert
|
||||
expect(testValue.Subject).toEqual("Safelite repair appointment");
|
||||
});
|
||||
test("getappointmentData should return expected model value for appointmentType IN_SHOP.", () => {
|
||||
//Arrange
|
||||
store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.IN_SHOP;
|
||||
store.getters.order.damage.isRepair = false;
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
const testValue = wrapper.vm.getappointmentData();
|
||||
|
||||
//Assert
|
||||
expect(testValue.Subject).toEqual("Safelite repair appointment");
|
||||
});
|
||||
test("isSameDayDropOff should return true for current schedule date.", () => {
|
||||
//Arrange
|
||||
store.getters.order.schedule.date = new Date().toISOString().split("T")[0];
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
const testValue = wrapper.vm.isSameDayDropOff();
|
||||
|
||||
//Assert
|
||||
expect(testValue).toEqual(true);
|
||||
});
|
||||
test("setCalendarAppointment should call getAppointment method for iCAL and Outlook.", () => {
|
||||
//Arrange
|
||||
const newValue = { name: calendarOptions.OUTLOOK };
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.getAppointment = jest.fn();
|
||||
//Act
|
||||
wrapper.vm.setCalendarAppointment(newValue);
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.getAppointment).toHaveBeenCalled();
|
||||
});
|
||||
test("setCalendarAppointment should call window.open for Google, yahoo and Outlook.com.", () => {
|
||||
//Arrange
|
||||
const newValue = { name: calendarOptions.GOOGLE };
|
||||
const { wrapper } = setupMocks({});
|
||||
window.open = jest.fn();
|
||||
|
||||
//Act
|
||||
wrapper.vm.setCalendarAppointment(newValue);
|
||||
|
||||
//Assert
|
||||
expect(window.open).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks({ customMountOptions }) {
|
||||
const mountOptions = getMountOptions({
|
||||
...customMountOptions,
|
||||
});
|
||||
|
||||
const wrapper = shallowMount(addToCalendar, mountOptions);
|
||||
|
||||
//wrapper.vm.setCmsContent = jest.fn();
|
||||
//wrapper.vm.$refs.datePicker.initializeComponent = jest.fn();
|
||||
//wrapper.vm.$refs.locationAlerts.initializeComponent = jest.fn();
|
||||
//wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.calendarModalQuestion.openModal = jest.fn();
|
||||
|
||||
return { wrapper };
|
||||
}
|
||||
|
|
@ -1,300 +0,0 @@
|
|||
<template>
|
||||
<div class="calendar-section">
|
||||
<div class="add-to-calendar">
|
||||
<button v-on:click="openCalendarModal">
|
||||
<span class="add-to-calendar-span">
|
||||
<img src="@/assets/img/icons/add-to-calendar.svg" class="add-to-calendar-img" />
|
||||
<span class="add-to-calendar-text">Add to calendar</span></span
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<calendarModalQuestion
|
||||
ref="calendarModalQuestion"
|
||||
customComponentId="calendarModalQuestion"
|
||||
cmsWidgetName="CalendarModalQuestion"
|
||||
@calendar-modal-closed="calendarModalClosed"
|
||||
:calendarOptionsData="calendarValues"
|
||||
v-model="selectedCalendarOption">
|
||||
</calendarModalQuestion>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { calendarOptions } from "@/constants/calendar-options";
|
||||
import { calendarStatus } from "@/constants/calendar-status";
|
||||
import calendarModalQuestion from "@/layouts/add-to-calendar/calendar-modal-question/calendar-modal-question";
|
||||
import {
|
||||
getDateFormat,
|
||||
calculateDuration,
|
||||
combineDateAndTime,
|
||||
shortTimeString,
|
||||
addMinutes,
|
||||
} from "@/helpers/date-helper";
|
||||
import { getCalendarFile, download } from "@/helpers/add-to-calendar-helper";
|
||||
import { AppointmentTypeStrings, RouteCodeFlags } from "@/constants/schedule-constants";
|
||||
import store from "@/store";
|
||||
|
||||
export default {
|
||||
name: "add-to-calendar",
|
||||
components: {
|
||||
calendarModalQuestion,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
appointment: this.getappointmentData(),
|
||||
selectedCalendarOption: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
calendarValues: {
|
||||
get: function () {
|
||||
var obj = [];
|
||||
const options = Object.values(calendarOptions);
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
obj[i] = this.getCalendarData(options[i]);
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedCalendarOption(newValue) {
|
||||
if (newValue) {
|
||||
this.setCalendarAppointment(newValue);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
setCalendarAppointment(calendarOption) {
|
||||
if (
|
||||
calendarOption.name.includes(calendarOptions.ICAL) ||
|
||||
calendarOption.name.includes(calendarOptions.OUTLOOK)
|
||||
) {
|
||||
this.getAppointment();
|
||||
} else {
|
||||
window.open(calendarOption.url);
|
||||
}
|
||||
},
|
||||
calendarModalClosed() {
|
||||
// Clear the selectedOption if close the modal
|
||||
this.selectedCalendarOption = null;
|
||||
},
|
||||
openCalendarModal() {
|
||||
this.$refs["calendarModalQuestion"].openModal();
|
||||
},
|
||||
scheduleDate() {
|
||||
return store.getters.order.schedule.date;
|
||||
},
|
||||
scheduleStartTime() {
|
||||
return store.getters.order.schedule.startTime;
|
||||
},
|
||||
scheduleEndTime() {
|
||||
return store.getters.order.schedule.endTime;
|
||||
},
|
||||
serviceLocationAddress() {
|
||||
return store.getters.order.serviceLocation.address;
|
||||
},
|
||||
ServiceLocationAddress2() {
|
||||
return store.getters.order.serviceLocation.address2;
|
||||
},
|
||||
serviceLocationCity() {
|
||||
return store.getters.order.serviceLocation.city;
|
||||
},
|
||||
serviceLocationState() {
|
||||
return store.getters.order.serviceLocation.state;
|
||||
},
|
||||
serviceLocationZipCode() {
|
||||
return store.getters.order.serviceLocation.zipCode;
|
||||
},
|
||||
providerAddress() {
|
||||
return store.getters.order.serviceLocation.provider.address.streetAddress;
|
||||
},
|
||||
providerCity() {
|
||||
return store.getters.order.serviceLocation.provider.address.city;
|
||||
},
|
||||
providerState() {
|
||||
return store.getters.order.serviceLocation.provider.address.state;
|
||||
},
|
||||
providerZipCode() {
|
||||
return store.getters.order.serviceLocation.provider.address.zipCode;
|
||||
},
|
||||
appointmentType() {
|
||||
return store.getters.order.serviceLocation.appointmentType;
|
||||
},
|
||||
uniqueId() {
|
||||
return store.getters.order.referralNumber?.toString();
|
||||
},
|
||||
serviceType() {
|
||||
const isRepair = store.getters.order.damage.isRepair;
|
||||
const funnelHasRecalibrationPart = store.getters.funnelHasRecalibrationPart;
|
||||
if (isRepair) {
|
||||
if (funnelHasRecalibrationPart) {
|
||||
return "replacement and recalibration";
|
||||
} else {
|
||||
return "replacement";
|
||||
}
|
||||
}
|
||||
return "repair";
|
||||
},
|
||||
routeCode() {
|
||||
return store.getters.order.schedule.routeCode;
|
||||
},
|
||||
isSameDayDropOff() {
|
||||
const todaysDate = new Date().toISOString().split("T")[0];
|
||||
return this.scheduleDate() === todaysDate;
|
||||
},
|
||||
getCalendarData(type) {
|
||||
var URL = "";
|
||||
const dateFormat = "yyyyMMddTHHmmss";
|
||||
const outlookComTimeSpanFormat = "yyyy-MM-ddTHH:mm:ss";
|
||||
if (type == calendarOptions.OUTLOOKCOM) {
|
||||
URL = `https://outlook.office.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent&startdt=${encodeURIComponent(
|
||||
getDateFormat(this.appointment.StartDate, outlookComTimeSpanFormat)
|
||||
)}&enddt=${encodeURIComponent(
|
||||
getDateFormat(this.appointment.EndDate, outlookComTimeSpanFormat)
|
||||
)}&subject=${encodeURIComponent(
|
||||
this.appointment.Subject
|
||||
)}&body=${encodeURIComponent(this.appointment.Body)}&location=${encodeURIComponent(
|
||||
this.appointment.Location
|
||||
)}`;
|
||||
}
|
||||
if (type == calendarOptions.GOOGLE) {
|
||||
URL = `https://www.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent(
|
||||
this.appointment.Subject
|
||||
)}&dates=${encodeURIComponent(
|
||||
getDateFormat(this.appointment.StartDate, dateFormat)
|
||||
)}/${encodeURIComponent(
|
||||
getDateFormat(this.appointment.EndDate, dateFormat)
|
||||
)}&details=${encodeURIComponent(
|
||||
this.appointment.Body
|
||||
)}&location=${encodeURIComponent(this.appointment.Location)}&sf=true&output=xml`;
|
||||
}
|
||||
if (type == calendarOptions.YAHOO) {
|
||||
URL = `https://calendar.yahoo.com/?v=60&TITLE=${encodeURIComponent(
|
||||
this.appointment.Subject
|
||||
)}&DESC=${encodeURIComponent(this.appointment.Body)}&ST=${encodeURIComponent(
|
||||
getDateFormat(this.appointment.StartDate, dateFormat)
|
||||
)}&DUR=${this.appointment.Duration}&in_loc=${encodeURIComponent(
|
||||
this.appointment.Location
|
||||
)}`;
|
||||
}
|
||||
return {
|
||||
url: URL,
|
||||
name: type,
|
||||
icon: require(`@/assets/img/icons/${type}.svg`),
|
||||
};
|
||||
},
|
||||
getappointmentData() {
|
||||
var subject = "";
|
||||
var location = "";
|
||||
var startDateTime = combineDateAndTime(this.scheduleDate(), this.scheduleStartTime());
|
||||
var endDateTime = combineDateAndTime(this.scheduleDate(), this.scheduleEndTime());
|
||||
var body = "";
|
||||
var isHTML = false;
|
||||
var duration = "";
|
||||
|
||||
if (this.appointmentType() === AppointmentTypeStrings.MOBILE) {
|
||||
location = `${
|
||||
this.ServiceLocationAddress2() ? this.ServiceLocationAddress2() + ", " : ""
|
||||
}${this.serviceLocationAddress()}, ${this.serviceLocationCity()}, ${this.serviceLocationState()} ${this.serviceLocationZipCode()}`;
|
||||
subject = `Safelite ${this.serviceType()} appointment`;
|
||||
|
||||
body =
|
||||
"Your safety is our top priority, so we'll let you know who's coming to you and your tech will reach out to let you know when they're on the way.";
|
||||
} else {
|
||||
location = `${this.providerAddress()}, ${this.providerCity()}, ${this.providerState()} ${this.providerZipCode()}`;
|
||||
if (this.appointmentType() === AppointmentTypeStrings.DROP_OFF) {
|
||||
if (this.routeCode().includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
|
||||
subject = "Drop off your vehicle by 5:30 PM";
|
||||
|
||||
body =
|
||||
"Drop off your vehicle by 5:30 PM the night of your scheduled appointment. The shop will work with you on a convenient pick-up time.";
|
||||
} else if (this.routeCode().includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
|
||||
if (this.isSameDayDropOff()) {
|
||||
subject = "Drop off your vehicle in the next 30 - 60 minutes";
|
||||
endDateTime = addMinutes(startDateTime, 120);
|
||||
body =
|
||||
"Drop your vehicle off in the next 30 - 60 minutes. We'll work your vehicle into our day's schedule. The shop will work with you on a convenient pick-up time.";
|
||||
} else {
|
||||
endDateTime = addMinutes(startDateTime, 120);
|
||||
subject =
|
||||
"Drop off your vehicle between " +
|
||||
shortTimeString(startDateTime) +
|
||||
" - " +
|
||||
shortTimeString(endDateTime);
|
||||
|
||||
body = `Drop off your vehicle by ${shortTimeString(
|
||||
endDateTime
|
||||
)} and pick it up later in the day. When you arrive, park in an open space and follow the key drop instructions. We'll text you when your vehicle is ready.`;
|
||||
}
|
||||
} //normal time slot
|
||||
else {
|
||||
subject = `Safelite ${this.serviceType()} appointment`;
|
||||
body = "Just a reminder that you're coming to us at " + location + ".";
|
||||
}
|
||||
} else {
|
||||
subject = `Safelite ${this.serviceType()} appointment`;
|
||||
body = "Just a reminder that you're coming to us at " + location + ".";
|
||||
}
|
||||
duration = calculateDuration(startDateTime, endDateTime);
|
||||
}
|
||||
return {
|
||||
Subject: subject,
|
||||
Location: location,
|
||||
StartDate: startDateTime,
|
||||
EndDate: endDateTime,
|
||||
RefrrlSeqNum: this.uniqueId(),
|
||||
Body: body,
|
||||
IsHTML: isHTML,
|
||||
Duration: duration,
|
||||
};
|
||||
},
|
||||
|
||||
getAppointment() {
|
||||
let body = this.appointment.Body;
|
||||
body = body.replace("|", "<").replace("~", ">");
|
||||
|
||||
const calFile = {
|
||||
Location: this.appointment.Location,
|
||||
Body: body,
|
||||
IsHTML: this.appointment.IsHTML,
|
||||
UniqueId: this.appointment.RefrrlSeqNum,
|
||||
Subject: this.appointment.Subject,
|
||||
StartDate: this.appointment.StartDate,
|
||||
EndDate: this.appointment.EndDate,
|
||||
Status: calendarStatus.BUSY,
|
||||
};
|
||||
var calBody = getCalendarFile(calFile);
|
||||
download("SafeliteAppointment.ics", calBody);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.add-to-calendar-text {
|
||||
margin: 0 0 0 7px;
|
||||
color: #1574a1;
|
||||
vertical-align: middle;
|
||||
font-weight: 500;
|
||||
font-family: "Roboto";
|
||||
}
|
||||
.add-to-calendar button {
|
||||
border: 0;
|
||||
background-color: #fff;
|
||||
}
|
||||
.calendar-section {
|
||||
display: table;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.calendar-section .add-to-calendar {
|
||||
padding-top: 15px;
|
||||
}
|
||||
.calendar-section .add-to-calendar .add-to-calendar-span {
|
||||
cursor: pointer;
|
||||
}
|
||||
.calendar-section .add-to-calendar .add-to-calendar-img {
|
||||
display: inline;
|
||||
width: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
|
|
|
|||
196
src/layouts/confirmation/add-to-calendar/add-to-calendar.spec.js
Normal file
196
src/layouts/confirmation/add-to-calendar/add-to-calendar.spec.js
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
// Components
|
||||
import addToCalendar from "@/layouts/confirmation/add-to-calendar/add-to-calendar.vue";
|
||||
import { calendarOptions } from "@/constants/calendar-options";
|
||||
import { AppointmentTypeStrings, RouteCodeFlags } from "@/constants/schedule-constants";
|
||||
|
||||
// Supporting Files
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import store from "@/store";
|
||||
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
jest.clearAllMocks();
|
||||
store.getters = {
|
||||
order: {
|
||||
schedule: {
|
||||
date: "2019-01-01",
|
||||
startTime: "09:00",
|
||||
endTime: "10:00",
|
||||
routeCode: "000",
|
||||
},
|
||||
lineItems: {
|
||||
glassParts: [
|
||||
{
|
||||
partNumber: "ABC123",
|
||||
},
|
||||
],
|
||||
supportingItems: [],
|
||||
},
|
||||
serviceLocation: {
|
||||
address: "",
|
||||
address2: null,
|
||||
city: "",
|
||||
state: "AZ",
|
||||
appointmentType: "Inshop",
|
||||
zipCode: "12345",
|
||||
zipCodeCtu: "01234",
|
||||
provider: {
|
||||
providerNumber: "123",
|
||||
address: {
|
||||
streetAddress: "test1",
|
||||
city: "test",
|
||||
state: "AZ",
|
||||
zipCode: "12345",
|
||||
zipCodeCtu: "01234",
|
||||
},
|
||||
},
|
||||
},
|
||||
damage: {
|
||||
isRepair: false,
|
||||
},
|
||||
referralNumber: "1234567",
|
||||
},
|
||||
payment: {
|
||||
isInsurance: true,
|
||||
},
|
||||
lineItems: {
|
||||
glassParts: [],
|
||||
supportingItems: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
afterEach(() => {
|
||||
store.getters = {};
|
||||
jest.restoreAllMocks();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("Add-to-calendar methods...", () => {
|
||||
test("Add-to-calendar should trigger openModal method", () => {
|
||||
//Arrange
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
customMountOptions: {
|
||||
propsData: {
|
||||
appointment: {
|
||||
RefrrlSeqNum: "123456",
|
||||
StartDate: new Date(2023, 9, 11, 12, 0, 0),
|
||||
EndDate: new Date(2023, 9, 11, 13, 0, 0),
|
||||
Subject: "Meeting with John",
|
||||
Location: "Conference Room",
|
||||
Body: "Discuss the project progress",
|
||||
IsHTML: true,
|
||||
Duration: "1220",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
//Act
|
||||
wrapper.vm.openCalendarModal();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.$refs.calendarModalQuestion.openModal).toBeCalled();
|
||||
});
|
||||
|
||||
test("getCalendarData should return expected model value.", () => {
|
||||
//Arrange
|
||||
const type = calendarOptions.OUTLOOKCOM;
|
||||
const { wrapper } = setupMocks({
|
||||
customMountOptions: {
|
||||
propsData: {
|
||||
appointment: {
|
||||
RefrrlSeqNum: "123456",
|
||||
StartDate: new Date(2023, 9, 11, 12, 0, 0),
|
||||
EndDate: new Date(2023, 9, 11, 13, 0, 0),
|
||||
Subject: "Meeting with John",
|
||||
Location: "Conference Room",
|
||||
Body: "Discuss the project progress",
|
||||
IsHTML: true,
|
||||
Duration: "1220",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
//Act
|
||||
const testValue = wrapper.vm.getCalendarData(type);
|
||||
|
||||
//Assert
|
||||
expect(testValue.name).toEqual(calendarOptions.OUTLOOKCOM);
|
||||
});
|
||||
|
||||
test("setCalendarAppointment should call getAppointment method for iCAL and Outlook.", () => {
|
||||
//Arrange
|
||||
const newValue = { name: calendarOptions.OUTLOOK };
|
||||
const { wrapper } = setupMocks({
|
||||
customMountOptions: {
|
||||
propsData: {
|
||||
appointment: {
|
||||
RefrrlSeqNum: "123456",
|
||||
StartDate: new Date(2023, 9, 11, 12, 0, 0),
|
||||
EndDate: new Date(2023, 9, 11, 13, 0, 0),
|
||||
Subject: "Meeting with John",
|
||||
Location: "Conference Room",
|
||||
Body: "Discuss the project progress",
|
||||
IsHTML: true,
|
||||
Duration: "1220",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
wrapper.vm.getAppointment = jest.fn();
|
||||
//Act
|
||||
wrapper.vm.setCalendarAppointment(newValue);
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.getAppointment).toHaveBeenCalled();
|
||||
});
|
||||
test("setCalendarAppointment should call window.open for Google, yahoo and Outlook.com.", () => {
|
||||
//Arrange
|
||||
const newValue = { name: calendarOptions.GOOGLE };
|
||||
const { wrapper } = setupMocks({
|
||||
customMountOptions: {
|
||||
propsData: {
|
||||
appointment: {
|
||||
RefrrlSeqNum: "123456",
|
||||
StartDate: new Date(2023, 9, 11, 12, 0, 0),
|
||||
EndDate: new Date(2023, 9, 11, 13, 0, 0),
|
||||
Subject: "Meeting with John",
|
||||
Location: "Conference Room",
|
||||
Body: "Discuss the project progress",
|
||||
IsHTML: true,
|
||||
Duration: "1220",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
window.open = jest.fn();
|
||||
|
||||
//Act
|
||||
wrapper.vm.setCalendarAppointment(newValue);
|
||||
|
||||
//Assert
|
||||
expect(window.open).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
var appointmentText = "Appointment content";
|
||||
function setupMocks({ customMountOptions }) {
|
||||
const mountOptions = getMountOptions({
|
||||
...customMountOptions,
|
||||
});
|
||||
mountOptions.mixins = [
|
||||
{
|
||||
methods: {
|
||||
getCmsContent: jest.fn().mockImplementation(() => {
|
||||
return appointmentText;
|
||||
}),
|
||||
},
|
||||
},
|
||||
];
|
||||
const wrapper = shallowMount(addToCalendar, mountOptions);
|
||||
wrapper.vm.$refs.calendarModalQuestion.openModal = jest.fn();
|
||||
return { wrapper };
|
||||
}
|
||||
314
src/layouts/confirmation/add-to-calendar/add-to-calendar.vue
Normal file
314
src/layouts/confirmation/add-to-calendar/add-to-calendar.vue
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
<template>
|
||||
<div class="calendar-section">
|
||||
<div class="add-to-calendar">
|
||||
<button v-on:click="openCalendarModal">
|
||||
<span class="add-to-calendar-span">
|
||||
<img src="@/assets/img/icons/add-to-calendar.svg" class="add-to-calendar-img" />
|
||||
<span class="add-to-calendar-text">Add to calendar</span></span
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<calendarModalQuestion
|
||||
ref="calendarModalQuestion"
|
||||
customComponentId="calendarModalQuestion"
|
||||
cmsWidgetName="CalendarModalQuestion"
|
||||
@calendar-modal-closed="calendarModalClosed"
|
||||
:calendarOptionsData="calendarValues"
|
||||
v-model="selectedCalendarOption">
|
||||
</calendarModalQuestion>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { calendarOptions } from "@/constants/calendar-options";
|
||||
import { calendarStatus } from "@/constants/calendar-status";
|
||||
import calendarModalQuestion from "@/layouts/confirmation/add-to-calendar/calendar-modal-question/calendar-modal-question";
|
||||
import { getDateFormat } from "@/helpers/date-helper";
|
||||
import { AppointmentTypeStrings, RouteCodeFlags } from "@/constants/schedule-constants";
|
||||
import { getCalendarFile, download } from "@/helpers/add-to-calendar-helper";
|
||||
import {
|
||||
calculateDuration,
|
||||
combineDateAndTime,
|
||||
shortTimeString,
|
||||
addMinutes,
|
||||
} from "@/helpers/date-helper";
|
||||
import store from "@/store";
|
||||
import { serviceType } from "@/constants/service-type";
|
||||
import { applicationConfig } from "@/constants/application-config.js";
|
||||
export default {
|
||||
name: "add-to-calendar",
|
||||
components: {
|
||||
calendarModalQuestion,
|
||||
},
|
||||
props: {
|
||||
mobileWidgetName: String,
|
||||
inShopWidgetName: String,
|
||||
dropOffWidgetName: String,
|
||||
overnightDropOffWidgetName: String,
|
||||
allDayDropOffWidgetName: String,
|
||||
sameDayDropOffWidgetName: String,
|
||||
serviceLocationFullAddress: String,
|
||||
providerFullAddress: String,
|
||||
appointmentType: String,
|
||||
scheduleDate: String,
|
||||
scheduleStartTime: String,
|
||||
scheduleEndTime: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedCalendarOption: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
calendarValues: {
|
||||
get: function () {
|
||||
var obj = [];
|
||||
const options = Object.values(calendarOptions);
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
obj[i] = this.getCalendarData(options[i]);
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
},
|
||||
IsSameDayDropOff() {
|
||||
const todaysDate = new Date().toISOString().split("T")[0];
|
||||
return this.scheduleDate === todaysDate;
|
||||
},
|
||||
AddToCalendar_Mobile_Subject() {
|
||||
return this.getCmsContent(this.mobileWidgetName, "HeaderText");
|
||||
},
|
||||
AddToCalendar_Mobile_Body() {
|
||||
return this.getCmsContent(this.mobileWidgetName, "BodyText");
|
||||
},
|
||||
AddToCalendar_InShop_Subject() {
|
||||
return this.getCmsContent(this.inShopWidgetName, "HeaderText");
|
||||
},
|
||||
AddToCalendar_InShop_Body() {
|
||||
return this.getCmsContent(this.inShopWidgetName, "BodyText");
|
||||
},
|
||||
AddToCalendar_DropOff_Subject() {
|
||||
return this.getCmsContent(this.dropOffWidgetName, "HeaderText");
|
||||
},
|
||||
AddToCalendar_DropOff_Body() {
|
||||
return this.getCmsContent(this.dropOffWidgetName, "BodyText");
|
||||
},
|
||||
AddToCalendar_OvernightDropOff_Subject() {
|
||||
return this.getCmsContent(this.overnightDropOffWidgetName, "HeaderText");
|
||||
},
|
||||
AddToCalendar_OvernightDropOff_Body() {
|
||||
return this.getCmsContent(this.overnightDropOffWidgetName, "BodyText");
|
||||
},
|
||||
AddToCalendar_AllDayDropOff_Subject() {
|
||||
return this.getCmsContent(this.allDayDropOffWidgetName, "HeaderText");
|
||||
},
|
||||
AddToCalendar_AllDayDropOff_Body() {
|
||||
return this.getCmsContent(this.allDayDropOffWidgetName, "BodyText");
|
||||
},
|
||||
AddToCalendar_SameDayDropOff_Subject() {
|
||||
return this.getCmsContent(this.sameDayDropOffWidgetName, "HeaderText");
|
||||
},
|
||||
AddToCalendar_SameDayDropOff_Body() {
|
||||
return this.getCmsContent(this.sameDayDropOffWidgetName, "BodyText");
|
||||
},
|
||||
UniqueId() {
|
||||
return store.getters.order.referralNumber?.toString();
|
||||
},
|
||||
ServiceType() {
|
||||
const isRepair = store.getters.order.damage.isRepair;
|
||||
const funnelHasRecalibrationPart = store.getters.funnelHasRecalibrationPart;
|
||||
if (!isRepair) {
|
||||
if (funnelHasRecalibrationPart) {
|
||||
return serviceType.REPLACEMENT_AND_RECALIBRATION;
|
||||
} else {
|
||||
return serviceType.REPLACEMENT;
|
||||
}
|
||||
}
|
||||
return serviceType.REPAIR;
|
||||
},
|
||||
RouteCode() {
|
||||
return store.getters.order.schedule.routeCode;
|
||||
},
|
||||
Appointment() {
|
||||
var subject = "";
|
||||
var location = "";
|
||||
var startDateTime = combineDateAndTime(this.scheduleDate, this.scheduleStartTime);
|
||||
var endDateTime = combineDateAndTime(this.scheduleDate, this.scheduleEndTime);
|
||||
var body = "";
|
||||
var isHTML = false;
|
||||
var duration = "";
|
||||
|
||||
if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||
location = this.serviceLocationFullAddress?.replace("<br/>", "");
|
||||
subject = this.AddToCalendar_Mobile_Subject?.replace(
|
||||
"{custom:SERVICETYPE}",
|
||||
this.ServiceType
|
||||
);
|
||||
body = this.AddToCalendar_Mobile_Body;
|
||||
} else {
|
||||
location = this.providerFullAddress?.replace("<br/>", "");
|
||||
if (this.appointmentType === AppointmentTypeStrings.DROP_OFF) {
|
||||
if (this.RouteCode.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
|
||||
subject = this.AddToCalendar_OvernightDropOff_Subject;
|
||||
body = this.AddToCalendar_OvernightDropOff_Body;
|
||||
} else if (this.RouteCode.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
|
||||
if (this.IsSameDayDropOff) {
|
||||
subject = this.AddToCalendar_SameDayDropOff_Subject;
|
||||
endDateTime = addMinutes(startDateTime, 120);
|
||||
body = this.AddToCalendar_SameDayDropOff_Body;
|
||||
} else {
|
||||
endDateTime = addMinutes(startDateTime, 120);
|
||||
subject = this.AddToCalendar_AllDayDropOff_Subject?.replace(
|
||||
"{custom:STARTDATETIME}",
|
||||
shortTimeString(startDateTime)
|
||||
).replace("{custom:ENDDATETIME}", shortTimeString(endDateTime));
|
||||
body = this.AddToCalendar_AllDayDropOff_Body?.replace(
|
||||
"{custom:ENDDATETIME}",
|
||||
shortTimeString(endDateTime)
|
||||
);
|
||||
}
|
||||
} //normal time slot
|
||||
else {
|
||||
subject = this.AddToCalendar_DropOff_Subject?.replace(
|
||||
"{custom:SERVICETYPE}",
|
||||
this.ServiceType
|
||||
);
|
||||
body = this.AddToCalendar_DropOff_Body?.replace(
|
||||
"{custom:ADDRESS}",
|
||||
location
|
||||
);
|
||||
}
|
||||
} else {
|
||||
subject = this.AddToCalendar_InShop_Subject?.replace(
|
||||
"{custom:SERVICETYPE}",
|
||||
this.ServiceType
|
||||
);
|
||||
body = this.AddToCalendar_InShop_Body?.replace("{custom:ADDRESS}", location);
|
||||
}
|
||||
duration = calculateDuration(startDateTime, endDateTime);
|
||||
}
|
||||
return {
|
||||
Subject: subject,
|
||||
Location: location,
|
||||
StartDate: startDateTime,
|
||||
EndDate: endDateTime,
|
||||
RefrrlSeqNum: this.UniqueId,
|
||||
Body: body,
|
||||
IsHTML: isHTML,
|
||||
Duration: duration,
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedCalendarOption(newValue) {
|
||||
if (newValue) {
|
||||
this.setCalendarAppointment(newValue);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
setCalendarAppointment(calendarOption) {
|
||||
if (
|
||||
calendarOption.name == calendarOptions.ICAL ||
|
||||
calendarOption.name == calendarOptions.OUTLOOK
|
||||
) {
|
||||
this.getAppointment();
|
||||
} else {
|
||||
window.open(calendarOption.url);
|
||||
}
|
||||
},
|
||||
calendarModalClosed() {
|
||||
// Clear the selectedOption if close the modal
|
||||
this.selectedCalendarOption = null;
|
||||
},
|
||||
openCalendarModal() {
|
||||
this.$refs["calendarModalQuestion"].openModal();
|
||||
},
|
||||
getCalendarData(type) {
|
||||
var URL = "";
|
||||
const dateFormat = "yyyyMMddTHHmmss";
|
||||
const outlookComTimeSpanFormat = "yyyy-MM-ddTHH:mm:ss";
|
||||
if (type == calendarOptions.OUTLOOKCOM) {
|
||||
URL = `${applicationConfig.OUTLOOK_CALENDAR}&startdt=${encodeURIComponent(
|
||||
getDateFormat(this.Appointment.StartDate, outlookComTimeSpanFormat)
|
||||
)}&enddt=${encodeURIComponent(
|
||||
getDateFormat(this.Appointment.EndDate, outlookComTimeSpanFormat)
|
||||
)}&subject=${encodeURIComponent(
|
||||
this.Appointment.Subject
|
||||
)}&body=${encodeURIComponent(this.Appointment.Body)}&location=${encodeURIComponent(
|
||||
this.Appointment.Location
|
||||
)}`;
|
||||
}
|
||||
if (type == calendarOptions.GOOGLE) {
|
||||
URL = `${applicationConfig.GOOGLE_CALENDAR}&text=${encodeURIComponent(
|
||||
this.Appointment.Subject
|
||||
)}&dates=${encodeURIComponent(
|
||||
getDateFormat(this.Appointment.StartDate, dateFormat)
|
||||
)}/${encodeURIComponent(
|
||||
getDateFormat(this.Appointment.EndDate, dateFormat)
|
||||
)}&details=${encodeURIComponent(
|
||||
this.Appointment.Body
|
||||
)}&location=${encodeURIComponent(this.Appointment.Location)}&sf=true&output=xml`;
|
||||
}
|
||||
if (type == calendarOptions.YAHOO) {
|
||||
URL = `${applicationConfig.YAHOO_CALENDAR}&TITLE=${encodeURIComponent(
|
||||
this.Appointment.Subject
|
||||
)}&DESC=${encodeURIComponent(this.Appointment.Body)}&ST=${encodeURIComponent(
|
||||
getDateFormat(this.Appointment.StartDate, dateFormat)
|
||||
)}&DUR=${this.Appointment.Duration}&in_loc=${encodeURIComponent(
|
||||
this.Appointment.Location
|
||||
)}`;
|
||||
}
|
||||
return {
|
||||
url: URL,
|
||||
name: type,
|
||||
icon: require(`@/assets/img/icons/${type}.svg`),
|
||||
};
|
||||
},
|
||||
getAppointment() {
|
||||
let body = this.Appointment.Body;
|
||||
body = body?.replace("|", "<").replace("~", ">");
|
||||
|
||||
const calFile = {
|
||||
Location: this.Appointment.Location,
|
||||
Body: body,
|
||||
IsHTML: this.Appointment.IsHTML,
|
||||
UniqueId: this.Appointment.RefrrlSeqNum,
|
||||
Subject: this.Appointment.Subject,
|
||||
StartDate: this.Appointment.StartDate,
|
||||
EndDate: this.Appointment.EndDate,
|
||||
Status: calendarStatus.BUSY,
|
||||
};
|
||||
var calBody = getCalendarFile(calFile);
|
||||
download("SafeliteAppointment.ics", calBody);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.add-to-calendar-text {
|
||||
margin: 0 0 0 7px;
|
||||
color: #1574a1;
|
||||
vertical-align: middle;
|
||||
font-weight: 500;
|
||||
font-family: "Roboto";
|
||||
}
|
||||
.add-to-calendar button {
|
||||
border: 0;
|
||||
background-color: #fff;
|
||||
}
|
||||
.calendar-section {
|
||||
display: table;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.calendar-section .add-to-calendar {
|
||||
padding-top: 15px;
|
||||
}
|
||||
.calendar-section .add-to-calendar .add-to-calendar-span {
|
||||
cursor: pointer;
|
||||
}
|
||||
.calendar-section .add-to-calendar .add-to-calendar-img {
|
||||
display: inline;
|
||||
width: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import calendarModalQuestion from "@/layouts/add-to-calendar/calendar-modal-question/calendar-modal-question.vue";
|
||||
import calendarModalQuestion from "@/layouts/confirmation/add-to-calendar/calendar-modal-question/calendar-modal-question.vue";
|
||||
|
||||
describe("calendar-modal-question.vue", () => {
|
||||
test("When selected calendar option is changed, a correctly selectedCalendarOption should be emitted", async () => {
|
||||
|
|
@ -115,16 +115,16 @@ describe("confirmation.vue", () => {
|
|||
});
|
||||
});
|
||||
describe("computed properties...", () => {
|
||||
// test("ScheduleDateFormatted should return date in expected format.", () => {
|
||||
// //Arrange
|
||||
// const { wrapper } = setupMocks({});
|
||||
test("ScheduleDateFormatted should return date in expected format.", () => {
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// // Act
|
||||
// const testValue = wrapper.vm.ScheduleDateFormatted;
|
||||
// Act
|
||||
const testValue = wrapper.vm.ScheduleDateFormatted;
|
||||
|
||||
// // Assert
|
||||
// expect(testValue).toEqual("Sunday, January 1");
|
||||
// });
|
||||
// Assert
|
||||
expect(testValue).toEqual("Sunday, January 1");
|
||||
});
|
||||
test("ScheduleTimeFormatted should return mobile time in expected format.", () => {
|
||||
//Arrange
|
||||
store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.MOBILE;
|
||||
|
|
|
|||
|
|
@ -4,16 +4,12 @@
|
|||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<div>
|
||||
<div class="container-fluid pb-2">
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<div class="header-container">
|
||||
<img :src="ScheduleConfirmationImage" />
|
||||
<span v-html="ScheduleConfirmationText"></span>
|
||||
</div>
|
||||
<div
|
||||
class="appoinmenttext"
|
||||
v-html="this.getAppointmentWordingText"></div>
|
||||
|
||||
<div class="main">
|
||||
<div>
|
||||
<vehicleBanner
|
||||
|
|
@ -21,16 +17,26 @@
|
|||
:displayGenericVehicleImage="false" />
|
||||
</div>
|
||||
<div class="scheduleText">
|
||||
<p>{{ this.ScheduleDateFormatted }}</p>
|
||||
<p>{{ this.ScheduleTimeFormatted }}</p>
|
||||
<p>{{ ScheduleDateFormatted }}</p>
|
||||
<p>{{ ScheduleTimeFormatted }}</p>
|
||||
</div>
|
||||
<addToCalendar></addToCalendar>
|
||||
<addToCalendar
|
||||
mobileWidgetName="AddToCalendar_Mobile"
|
||||
inShopWidgetName="AddToCalendar_InShop"
|
||||
dropOffWidgetName="AddToCalendar_DropOff"
|
||||
overnightDropOffWidgetName="AddToCalendar_OvernightDropOff"
|
||||
allDayDropOffWidgetName="AddToCalendar_AllDayDropOff"
|
||||
sameDayDropOffWidgetName="AddToCalendar_SameDayDropOff"
|
||||
:serviceLocationFullAddress="ServiceLocationFullAddress"
|
||||
:providerFullAddress="ProviderFullAddress"
|
||||
:appointmentType="AppointmentType"
|
||||
:scheduleDate="ScheduleDate"
|
||||
:scheduleStartTime="ScheduleStartTime"
|
||||
:scheduleEndTime="ScheduleEndTime" />
|
||||
|
||||
<div
|
||||
class="appoinmenttext"
|
||||
v-html="this.AppointmentWordingText"></div>
|
||||
<div class="appoinmenttext" v-html="AppointmentWordingText"></div>
|
||||
</div>
|
||||
<div class="emailtext" v-html="this.ConfirmationEmailText"></div>
|
||||
<div class="emailtext" v-html="ConfirmationEmailText"></div>
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
|
|
@ -49,21 +55,18 @@
|
|||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import addToCalendar from "@/layouts/add-to-calendar/add-to-calendar";
|
||||
import addToCalendar from "@/layouts/confirmation/add-to-calendar/add-to-calendar";
|
||||
//Supporting files
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import { applicationConfig } from "@/constants/application-config.js";
|
||||
import { convertDateStringToDate } from "@/layouts/schedule/helpers/schedule-helper";
|
||||
|
||||
import { Form } from "vee-validate";
|
||||
import store from "@/store";
|
||||
|
||||
import {
|
||||
getFullDayName,
|
||||
getFullMonthName,
|
||||
get12HourTimeFormat,
|
||||
get12HourTimeMobileFormat,
|
||||
} from "@/helpers/date-helper";
|
||||
import { get12HourTimeFormat, get12HourTimeMobileFormat } from "@/helpers/date-helper";
|
||||
export default {
|
||||
name: "confirmation",
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
|
|
@ -93,9 +96,9 @@ export default {
|
|||
},
|
||||
ConfirmationEmailText() {
|
||||
return this.getCmsContent("ConfirmationEmailWidget", "BodyText")
|
||||
.replaceAll("{custom:MY_ACCOUNT_URL}", applicationConfig.MY_ACCOUNT)
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">");
|
||||
?.replaceAll("{custom:MY_ACCOUNT_URL}", applicationConfig.MY_ACCOUNT)
|
||||
?.replaceAll("<", "<")
|
||||
?.replaceAll(">", ">");
|
||||
},
|
||||
ScheduleDate() {
|
||||
return store.getters.order.schedule.date;
|
||||
|
|
@ -147,17 +150,17 @@ export default {
|
|||
},
|
||||
AppointmentWordingText() {
|
||||
if (this.AppointmentType == AppointmentTypeStrings.MOBILE) {
|
||||
return this.MobileWordingText.replaceAll(
|
||||
return this.MobileWordingText?.replaceAll(
|
||||
"{custom:ADDRESS}",
|
||||
this.ServiceLocationFullAddress
|
||||
);
|
||||
} else if (this.AppointmentType == AppointmentTypeStrings.DROP_OFF) {
|
||||
return this.DropOffWordingText.replaceAll(
|
||||
return this.DropOffWordingText?.replaceAll(
|
||||
"{custom:ADDRESS}",
|
||||
this.ProviderFullAddress
|
||||
);
|
||||
} else {
|
||||
return this.InShopWordingText.replaceAll(
|
||||
return this.InShopWordingText?.replaceAll(
|
||||
"{custom:ADDRESS}",
|
||||
this.ProviderFullAddress
|
||||
);
|
||||
|
|
@ -174,10 +177,14 @@ export default {
|
|||
return `${this.ProviderAddress},<br/> ${this.ProviderCity}, ${this.ProviderState} ${this.ProviderZipCode}`;
|
||||
},
|
||||
ScheduleDateFormatted() {
|
||||
const scheduleDate = new Date(this.ScheduleDate);
|
||||
return `${getFullDayName(scheduleDate)}, ${getFullMonthName(
|
||||
scheduleDate
|
||||
)} ${scheduleDate.getDate()}`;
|
||||
// This conversion ensures we don't get get GMT induced date changes
|
||||
const dateObject = convertDateStringToDate(this.ScheduleDate);
|
||||
// Ex: Tuesday, April 22
|
||||
return dateObject.toLocaleDateString("en-us", {
|
||||
weekday: "long",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
},
|
||||
ScheduleTimeFormatted() {
|
||||
if (this.AppointmentType == AppointmentTypeStrings.MOBILE) {
|
||||
|
|
@ -191,11 +198,6 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
workOrderNumber: store.getters.order.workOrderNumber,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
// Service Location
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@
|
|||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="my-5" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-4 mb-5" />
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="FirstNameWidget"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner cmsWidgetName="VehicleBannerWidget" />
|
||||
<funnelSubHeader class="pb-4" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
|
@ -33,12 +33,13 @@
|
|||
|
||||
<div v-else>
|
||||
<alert
|
||||
class="my-4"
|
||||
class="mt-4 mb-4"
|
||||
:cmsWidgetName="alertInfo"
|
||||
alertClass="alert-info"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
v-model="serviceZipCode"
|
||||
inputId="serviceZipCode"
|
||||
|
|
@ -47,6 +48,7 @@
|
|||
validationRules="zip-required|zip-format" />
|
||||
|
||||
<textboxQuestion
|
||||
class="mb-0"
|
||||
cmsWidgetName="EmailAddressQuestionWidget"
|
||||
v-model="emailAddress"
|
||||
inputId="emailAddress"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,164 @@
|
|||
<template>
|
||||
<transition name="fade" mode="out-in">
|
||||
<div class="add-vaps-buttons" aria-live="polite">
|
||||
<buttonQuestion
|
||||
ref="buttonQuestion"
|
||||
:answers="answersToDisplay"
|
||||
groupName="addVaps"
|
||||
v-model="answersToDisplay"
|
||||
buttonTypeString="addVapsButton"
|
||||
:buttonTypeObject="addVapsButton"
|
||||
:isWide="answersToDisplay.length > 1 ? false : true"
|
||||
@update:modelValue="openModal" />
|
||||
|
||||
<contentGroupModal
|
||||
ref="RainDefenseModal"
|
||||
cmsWidgetName="RainDefenseModal"
|
||||
v-if="rainDefenseItem"
|
||||
@footer-button-action="addToCart('RainDefenseModal', rainDefenseItem)"
|
||||
footerButtonActionName="footer-button-action">
|
||||
<p class="price">{{ rainDefenseItem.listPrice }}</p>
|
||||
</contentGroupModal>
|
||||
|
||||
<contentGroupModal
|
||||
ref="WipersModal"
|
||||
cmsWidgetName="WipersModal"
|
||||
v-if="wipersItem"
|
||||
@footer-button-action="addToCart('WipersModal', wipersItem)"
|
||||
footerButtonActionName="footer-button-action">
|
||||
<p class="price">{{ wipersItem.listPrice }}</p>
|
||||
</contentGroupModal>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import buttonQuestion from "@/digital-components/button-question/button-question";
|
||||
import addVapsButton from "./add-vaps-button/add-vaps-button";
|
||||
import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal";
|
||||
|
||||
export default {
|
||||
name: "add-vaps-modal-buttons",
|
||||
props: {
|
||||
vapsTilesCmsName: String,
|
||||
availableLineItems: Object,
|
||||
modelValue: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
addVapsButton: addVapsButton,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
openModal(modalName) {
|
||||
this.$refs[modalName].openModal();
|
||||
},
|
||||
addToCart(modalName, part) {
|
||||
const vapsItems = this.currentCartItems.vaps;
|
||||
const alreadyInVaps = vapsItems.some((item) => {
|
||||
return item.partType === part.partType;
|
||||
});
|
||||
if (!alreadyInVaps) {
|
||||
if (part.subItems) {
|
||||
// for wipers, a "combined" part (w/ subItems)
|
||||
part.subItems.forEach((part) => {
|
||||
this.currentCartItems.vaps.push(part);
|
||||
});
|
||||
} else {
|
||||
// for rain defense, a "single" part (w/o subItems)
|
||||
this.currentCartItems.vaps.push(part);
|
||||
}
|
||||
}
|
||||
this.$refs[modalName].closeModal();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
currentCartItems() {
|
||||
return this.modelValue;
|
||||
},
|
||||
showAddRainDefense() {
|
||||
// show if not in cart
|
||||
return !this.currentCartItems.vaps?.some((vap) => {
|
||||
return vap?.partType === "RAIN DEFENSE";
|
||||
});
|
||||
},
|
||||
showAddWipers() {
|
||||
// show if not in cart
|
||||
if (this.currentCartItems && this.currentCartItems.vaps) {
|
||||
return !this.currentCartItems.vaps.some((vap) => {
|
||||
return vap?.partType?.includes(" WIPER");
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
rainDefenseItem() {
|
||||
const lineItem = this.availableLineItems.find((item) => {
|
||||
return item.partType === "RAIN DEFENSE";
|
||||
});
|
||||
if (lineItem)
|
||||
lineItem.listPrice = parseFloat(
|
||||
lineItem?.laborAmount + lineItem?.sellingPrice + lineItem?.kitPrice
|
||||
).toFixed(2);
|
||||
return lineItem;
|
||||
},
|
||||
wipersItem() {
|
||||
const wiperLineItems = this.availableLineItems.filter((item) => {
|
||||
return item.partType.includes("WIPER");
|
||||
});
|
||||
let wiperLineItem;
|
||||
if (wiperLineItems.length === 1) {
|
||||
wiperLineItem = wiperLineItems[0];
|
||||
wiperLineItem.listPrice = parseFloat(
|
||||
wiperLineItem.laborAmount + wiperLineItem.sellingPrice + wiperLineItem.kitPrice
|
||||
).toFixed(2);
|
||||
return wiperLineItem;
|
||||
} else if (wiperLineItems.length > 1) {
|
||||
let combinedPrice = 0;
|
||||
wiperLineItems?.forEach((item) => {
|
||||
combinedPrice += item?.laborAmount + item?.sellingPrice + item?.kitPrice;
|
||||
});
|
||||
wiperLineItem = {
|
||||
description: "COMBINED ITEM",
|
||||
partType: "WIPERS",
|
||||
subItems: [],
|
||||
listPrice: parseFloat(combinedPrice).toFixed(2),
|
||||
};
|
||||
wiperLineItems?.forEach((item) => {
|
||||
wiperLineItem.subItems.push(item);
|
||||
});
|
||||
return wiperLineItem;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
answersCmsData() {
|
||||
return this.getCmsContent(this.vapsTilesCmsName, "Answers");
|
||||
},
|
||||
answersToDisplay() {
|
||||
const answers = [];
|
||||
if (!this.answersCmsData) return answers;
|
||||
const getAnswer = (name, answers) => {
|
||||
const answerIndex = answers.findIndex((answer) => {
|
||||
return answer.Name === name;
|
||||
});
|
||||
return answers[answerIndex];
|
||||
};
|
||||
if (this.showAddWipers) {
|
||||
const answer = getAnswer("WipersModal", this.answersCmsData);
|
||||
answer.SubText = "+$" + this.wipersItem.listPrice;
|
||||
answers.push(answer);
|
||||
}
|
||||
if (this.showAddRainDefense) {
|
||||
const answer = getAnswer("RainDefenseModal", this.answersCmsData);
|
||||
answer.SubText = "+$" + this.rainDefenseItem.listPrice;
|
||||
answers.push(answer);
|
||||
}
|
||||
return answers;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
buttonQuestion,
|
||||
contentGroupModal,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
:questionText="paymentMethodQuestionText"
|
||||
:answers="paymentMethodAnswerData"
|
||||
isRequired
|
||||
:validationRules="validationRules"
|
||||
v-model="selectedMethod"
|
||||
textPosition="text-start" />
|
||||
</div>
|
||||
|
|
@ -27,6 +28,7 @@ export default {
|
|||
},
|
||||
props: {
|
||||
modelValue: String,
|
||||
validationRules: String,
|
||||
},
|
||||
methods: {
|
||||
getAnswersNullSafe(widgetName) {
|
||||
|
|
|
|||
|
|
@ -7,42 +7,49 @@
|
|||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<funnelSubHeader class="mb-5" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<funnelSubHeader
|
||||
class="mb-5 mt-4"
|
||||
cmsWidgetName="FunnelSubHeaderWidget"
|
||||
leftAlignHeader />
|
||||
|
||||
<p v-if="isPiaDisabled" class="fw-normal no-pia-disclaimer">
|
||||
<span>{{ noPiaDisclaimer }}</span>
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<cart
|
||||
:damage="damageInfo"
|
||||
:availableLineItems="availableLineItems"
|
||||
:lineItems="lineItems"
|
||||
:lineItems="taxedLineItems"
|
||||
servicePackageOptionsCmsName="ServicePackageTitle"
|
||||
@cart-remove="cartRemove" />
|
||||
|
||||
<div>
|
||||
<alert
|
||||
ref="paypalErrorAlert"
|
||||
ref="piaErrorAlert"
|
||||
class="my-4"
|
||||
v-if="displayPaypalAlert"
|
||||
cmsWidgetName="PaypalErrorAlertWidget"
|
||||
v-if="displayPiaAlert"
|
||||
cmsWidgetName="PIAErrorAlertWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
</div>
|
||||
|
||||
<add-vaps-modal-buttons
|
||||
:cartItems="cartItems"
|
||||
v-model="lineItems"
|
||||
:availableLineItems="availableLineItems"
|
||||
vapsTilesCmsName="VapsProductTiles"
|
||||
v-on="{ 'buttonEvent.openModal': openModal }" />
|
||||
vapsTilesCmsName="VapsProductTiles" />
|
||||
|
||||
<button @click="openModal('RainDefenseModal')">OPEN MODAL</button>
|
||||
<contentGroupModal ref="RainDefenseModal" cmsWidgetName="RainDefenseModal" />
|
||||
<paymentMethodQuestion
|
||||
v-if="!isPiaDisabled"
|
||||
v-model="paymentMethodInternalModel" />
|
||||
v-model="paymentMethodInternalModel"
|
||||
validationRules="option-required" />
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
|
|
@ -65,11 +72,11 @@ import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-heade
|
|||
import paymentMethodQuestion from "@/layouts/payment-method/payment-method-question/payment-method-question";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
import cart from "@/fmg-components/cart/cart";
|
||||
import addVapsModalButtons from "@/fmg-components/add-vaps-modal-buttons/add-vaps-modal-buttons";
|
||||
import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal";
|
||||
import addVapsModalButtons from "./add-vaps-modal-buttons/add-vaps-modal-buttons";
|
||||
import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
|
||||
// Supporting Items
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
|
|
@ -79,8 +86,13 @@ import { paymentMethods } from "@/constants/payment-method-constants";
|
|||
import { experimentSettings } from "@/constants/experiments";
|
||||
|
||||
import { Form } from "vee-validate";
|
||||
import { defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
||||
|
||||
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
|
||||
|
||||
export default {
|
||||
name: "paymentMethod",
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
|
|
@ -97,7 +109,6 @@ export default {
|
|||
null,
|
||||
"payment-method"
|
||||
);
|
||||
console.log("rainDefensePromise: ", rainDefensePromise);
|
||||
|
||||
const supportingItemsPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_SUPPORTING_ITEMS,
|
||||
|
|
@ -126,7 +137,7 @@ export default {
|
|||
|
||||
const resultMap = await settleAllPromises(promiseResultMap);
|
||||
|
||||
const glassParts = (await store.getters.order.lineItems.glassParts) ?? [];
|
||||
const glassParts = store.getters.order.lineItems.glassParts ?? [];
|
||||
|
||||
const availableLineItems = [
|
||||
resultMap.rainDefense,
|
||||
|
|
@ -143,16 +154,35 @@ export default {
|
|||
"payment-method",
|
||||
false
|
||||
);
|
||||
const lineItems = store.getters.lineItems;
|
||||
|
||||
const cartItems = [...glassParts, ...lineItems.supportingItems, ...lineItems.vaps]; // TOBEREMOVED BY AJC IN CSR-1384
|
||||
const lineItems = store.getters.order.lineItems;
|
||||
|
||||
const combinedLineItems = [];
|
||||
combinedLineItems.push(lineItems.glassParts ?? []);
|
||||
combinedLineItems.push(lineItems.supportingItems ?? []);
|
||||
combinedLineItems.push(lineItems.vaps ?? []);
|
||||
|
||||
const taxedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||
{
|
||||
billToAccountNumber: "87291",
|
||||
providerNumber: store.getters.order.serviceLocation.provider.providerNumber,
|
||||
appointmentType: store.getters.order.serviceLocation.appointmentType,
|
||||
serviceLocationState: store.getters.order.serviceLocation.state,
|
||||
serviceLocationZipCode: store.getters.order.serviceLocation.zipCode,
|
||||
pricedLineItems: combinedLineItems,
|
||||
},
|
||||
"payment-method",
|
||||
false
|
||||
);
|
||||
|
||||
// Call the "next" function to complete the transition to this page.
|
||||
next((vm) => {
|
||||
vm.cartItems = cartItems; // TOBEREMOVED BY AJC IN CSR-1384
|
||||
vm.cartItems = combinedLineItems; // TOBEREMOVED BY AJC IN CSR-1384
|
||||
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
vm.availableLineItems = pricedAvailableLineItems;
|
||||
vm.taxedLineItems = taxedLineItems;
|
||||
vm.lineItems = lineItems;
|
||||
});
|
||||
},
|
||||
|
|
@ -161,9 +191,10 @@ export default {
|
|||
cartItems: [], // TOBEREMOVED BY AJC IN CSR-1384
|
||||
|
||||
lineItems: [],
|
||||
taxedLineItems: [], // temporary, for use by cart.vue
|
||||
availableLineItems: [],
|
||||
displayPaypalAlert: this.getPaypalAlert(),
|
||||
paymentMethodInternalModel: null,
|
||||
paymentMethodInternalModel: this.getPaymentMethodFromStore(),
|
||||
displayPiaAlert: this.getPiaAlert(),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -219,19 +250,22 @@ export default {
|
|||
packageReqs && serviceLocationReqs && isInsuranceSet && scheduleReqs && customerReqs
|
||||
);
|
||||
},
|
||||
openModal(modalName) {
|
||||
this.$refs[modalName].openModal();
|
||||
},
|
||||
getPaypalAlert() {
|
||||
getPiaAlert() {
|
||||
return store.getters.order.payment.piaErrorCode ? true : false;
|
||||
},
|
||||
vapsItemsSelectedAction(vapsItemsSelected) {
|
||||
this.cartItems = vapsItemsSelected;
|
||||
getPaymentMethodFromStore() {
|
||||
const isPia = store.getters.order.payment.isPia;
|
||||
|
||||
if (isPia === null || isPia === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isPia) {
|
||||
return store.getters.order.payment.piaType;
|
||||
} else {
|
||||
return paymentMethods.LATER;
|
||||
}
|
||||
},
|
||||
// TODO (as part of CSR-1384) enable adding to the cart
|
||||
// vapsItemsSelectedAction(vapsItemsSelected) {
|
||||
// this.cartItems = vapsItemsSelected;
|
||||
// },
|
||||
cartRemove(item) {
|
||||
const matchedIndices = [];
|
||||
this.cartItems.forEach((cartItem, i) => {
|
||||
|
|
@ -249,15 +283,25 @@ export default {
|
|||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
},
|
||||
async forwardButtonAction() {
|
||||
await this.dispatchStoreAction(
|
||||
storeActions.SAVE_PAYMENT_METHOD_CHOICE,
|
||||
this.paymentMethod,
|
||||
false
|
||||
);
|
||||
|
||||
await this.dispatchStoreAction(storeActions.SAVE_VAPS, this.lineItems.vaps, false);
|
||||
|
||||
await this.dispatchStoreAction(storeActions.SAVE_PROMOS, this.lineItems.promos, false);
|
||||
|
||||
switch (this.paymentMethod) {
|
||||
case paymentMethods.CREDIT_CARD:
|
||||
this.piaSetup("cc");
|
||||
this.piaSetup(this.paymentMethod);
|
||||
break;
|
||||
case paymentMethods.PAYPAL:
|
||||
this.piaSetup("pp");
|
||||
this.piaSetup(this.paymentMethod);
|
||||
break;
|
||||
case paymentMethods.AFTERPAY:
|
||||
this.piaSetup("ap");
|
||||
this.piaSetup(this.paymentMethod);
|
||||
break;
|
||||
default:
|
||||
this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, null, false);
|
||||
|
|
@ -269,21 +313,11 @@ export default {
|
|||
);
|
||||
}
|
||||
},
|
||||
async ccdPayment() {
|
||||
this.piaSetup("cc");
|
||||
},
|
||||
async paypal() {
|
||||
this.piaSetup("pp");
|
||||
},
|
||||
async afterpay() {
|
||||
this.piaSetup("ap");
|
||||
},
|
||||
async piaSetup(payNowType) {
|
||||
this.$refs.loadingModal.showModal();
|
||||
|
||||
// since we're leaving the site for pia, clear any save session promises that we will not be able to resolve when we return
|
||||
await this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
|
||||
await this.dispatchStoreAction(storeActions.SAVE_PAY_NOW_TYPE, payNowType, false);
|
||||
|
||||
// make sure pia error codes are reset
|
||||
this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, null, false);
|
||||
|
|
@ -303,9 +337,11 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
damageInfo() {
|
||||
// console.log("this.$store.getters.damage ", this.$store.getters.damage)
|
||||
return this.$store.getters.damage;
|
||||
},
|
||||
noPiaDisclaimer() {
|
||||
return this.getCmsContent("NoPiaDisclaimerWidget", "Text");
|
||||
},
|
||||
customCtaCopy() {
|
||||
switch (this.paymentMethod) {
|
||||
case paymentMethods.CREDIT_CARD:
|
||||
|
|
@ -349,8 +385,14 @@ export default {
|
|||
cart,
|
||||
alert,
|
||||
addVapsModalButtons,
|
||||
contentGroupModal,
|
||||
paymentMethodQuestion,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.no-pia-disclaimer {
|
||||
color: $gray-550;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -9,19 +9,20 @@ import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js"
|
|||
import { queryStrings } from "@/constants/query-strings";
|
||||
import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import store from "@/store";
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
import { Form } from "vee-validate";
|
||||
import { paymentMethods } from "@/constants/payment-method-constants";
|
||||
|
||||
export default {
|
||||
name: "payment-pia-return",
|
||||
async mounted() {
|
||||
this.$refs.loadingModal.showModal();
|
||||
const piaError = getQuerystringParameter(queryStrings.ERROR);
|
||||
const token = getQuerystringParameter(queryStrings.TOKEN);
|
||||
const payerId = getQuerystringParameter(queryStrings.PAYERID);
|
||||
|
||||
if (piaError) {
|
||||
console.log("Error during payment: " + piaError);
|
||||
baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.SAVE_PIA_ERROR_CODE,
|
||||
piaError,
|
||||
|
|
@ -29,33 +30,26 @@ export default {
|
|||
);
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.PIA_ERROR, this.$route);
|
||||
} else {
|
||||
if (token && payerId) {
|
||||
baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.SAVE_PIA_ERROR_CODE,
|
||||
null,
|
||||
false
|
||||
);
|
||||
baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.SAVE_WORK_ORDER_FLAG,
|
||||
true,
|
||||
false
|
||||
);
|
||||
|
||||
// Final work order submit after returning from PIA.
|
||||
await this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
|
||||
await submitWorkOrder({ pageNameToLog: "payment-pia-return" });
|
||||
this.$refs.loadingModal.isModalVisible = false;
|
||||
this.$router.navigateWithoutSaving(
|
||||
this.navigationScenarios.PIA_SUCCESS,
|
||||
this.$route
|
||||
);
|
||||
} else {
|
||||
baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.SAVE_PIA_ERROR_CODE,
|
||||
piaError,
|
||||
false
|
||||
);
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.PIA_ERROR, this.$route);
|
||||
switch (store.getters.order.payment.piaType) {
|
||||
case paymentMethods.CREDIT_CARD:
|
||||
case paymentMethods.AFTERPAY:
|
||||
await this.processCreditCardResponse();
|
||||
break;
|
||||
case paymentMethods.PAYPAL:
|
||||
await this.processPaypalResponse();
|
||||
break;
|
||||
default:
|
||||
var msg = "Unknown Pia type: " + store.getters.order.payment.piaType;
|
||||
console.log(msg);
|
||||
baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.SAVE_PIA_ERROR_CODE,
|
||||
msg,
|
||||
false
|
||||
);
|
||||
this.$router.navigateWithoutSaving(
|
||||
this.navigationScenarios.PIA_ERROR,
|
||||
this.$route
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -63,6 +57,72 @@ export default {
|
|||
arePagePrerequisitesValid() {
|
||||
return true;
|
||||
},
|
||||
async processPaypalResponse() {
|
||||
const token = getQuerystringParameter(queryStrings.TOKEN);
|
||||
const payerId = getQuerystringParameter(queryStrings.PAYERID);
|
||||
baseMixin.methods.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, null, false);
|
||||
baseMixin.methods.dispatchStoreAction(storeActions.SAVE_WORK_ORDER_FLAG, true, false);
|
||||
baseMixin.methods.dispatchStoreAction(storeActions.SAVE_PAYPAL_TOKEN, token, false);
|
||||
|
||||
await this.saveAndSubmitWorkOrder();
|
||||
},
|
||||
async processCreditCardResponse() {
|
||||
const subscriptionID = getQuerystringParameter(queryStrings.SUBSCRIPTIONID);
|
||||
baseMixin.methods.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, null, false);
|
||||
baseMixin.methods.dispatchStoreAction(storeActions.SAVE_WORK_ORDER_FLAG, true, false);
|
||||
|
||||
const referralSeqNum = getQuerystringParameter(queryStrings.REFERRAL_SEQ_NUM);
|
||||
if (referralSeqNum != store.getters.order.referralSequenceNumber) {
|
||||
console.log(
|
||||
"error: unknown ref:" +
|
||||
referralSeqNum +
|
||||
" " +
|
||||
store.getters.order.referralSequenceNumber
|
||||
);
|
||||
baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.SAVE_PIA_ERROR_CODE,
|
||||
"unknown error",
|
||||
false
|
||||
);
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.PIA_ERROR, this.$route);
|
||||
} else {
|
||||
const expMonth = getQuerystringParameter(queryStrings.CARD_EXPIRATION_MONTH);
|
||||
const expYear = getQuerystringParameter(queryStrings.CARD_EXPIRATION_YEAR);
|
||||
const cardType = getQuerystringParameter(queryStrings.CARD_TYPE);
|
||||
const billToPostalCode = getQuerystringParameter(queryStrings.BILL_TO_POSTAL_CODE);
|
||||
const billToFirstName = getQuerystringParameter(queryStrings.BILL_TO_FIRST_NAME);
|
||||
const billToLastName = getQuerystringParameter(queryStrings.BILL_TO_LAST_NAME);
|
||||
const referenceNumber = getQuerystringParameter(queryStrings.REFERENCE_NUMBER);
|
||||
const authCode = getQuerystringParameter(queryStrings.AUTH_CODE);
|
||||
const transactionId = getQuerystringParameter(queryStrings.TRANSACTION_ID);
|
||||
const transReferenceNumber = getQuerystringParameter(
|
||||
queryStrings.TRANS_REFERENCE_NUMBER
|
||||
);
|
||||
|
||||
var ccToken = {
|
||||
subscriptionId: subscriptionID,
|
||||
expMonth: expMonth,
|
||||
expYear: expYear,
|
||||
cardType: cardType,
|
||||
billToPostalCode: billToPostalCode,
|
||||
billToFirstName: billToFirstName,
|
||||
billToLastName: billToLastName,
|
||||
referenceNumber: referenceNumber,
|
||||
authCode: authCode,
|
||||
transactionId: transactionId,
|
||||
transReferenceNumber: transReferenceNumber,
|
||||
};
|
||||
baseMixin.methods.dispatchStoreAction(storeActions.SAVE_CCTOKEN, ccToken, false);
|
||||
await this.saveAndSubmitWorkOrder();
|
||||
}
|
||||
},
|
||||
async saveAndSubmitWorkOrder() {
|
||||
// Final work order submit after returning from PIA.
|
||||
await this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
|
||||
await submitWorkOrder({ pageNameToLog: "payment-pia-return" });
|
||||
this.$refs.loadingModal.isModalVisible = false;
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.PIA_SUCCESS, this.$route);
|
||||
},
|
||||
forwardButtonAction() {
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.PIA_SUCCESS, this.$route);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,31 +1,35 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<loadingModal notFullScreen ref="loadingModal" />
|
||||
<div class="page-container-grouped-styles">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" v-if="!isPaypal" />
|
||||
<funnelSubHeader
|
||||
class="mb-5"
|
||||
cmsWidgetName="FunnelSubHeaderWidget"
|
||||
leftAlignHeader
|
||||
v-if="!isPaypal" />
|
||||
<hr class="mt-0" />
|
||||
<div id="card-container">
|
||||
<iframe
|
||||
ref="paymentFrame"
|
||||
class="hop-iframe payment-iframe"
|
||||
name="card-frame"
|
||||
id="card-frame"
|
||||
seamless
|
||||
scrolling="no">
|
||||
</iframe>
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
:isBackButtonHidden="shouldHideBackButton"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<div id="card-container">
|
||||
<iframe
|
||||
ref="paymentFrame"
|
||||
class="hop-iframe payment-iframe"
|
||||
name="card-frame"
|
||||
id="card-frame"
|
||||
seamless
|
||||
scrolling="no">
|
||||
</iframe>
|
||||
</div>
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
:isBackButtonHidden="shouldHideBackButton"
|
||||
isSubmitHidden="true"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
@ -51,27 +55,12 @@
|
|||
<input type="hidden" name="sgNoKeystrokeProcessing" value="false" />
|
||||
|
||||
<input type="hidden" name="maskCharacter" value="*" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="styleSheetCode"
|
||||
value="https://fixmyglasstest.safelite.com/FixMyGlass/css/experiments/site-2020Styling.css" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="styleSheetCode2"
|
||||
value="https://fixmyglasstest.safelite.com/FixMyGlass/css/experiments/SLHop-2020Styling.css" />
|
||||
<input type="hidden" name="styleSheetCode" :value="dynamicCSSUrl" />
|
||||
<input type="hidden" name="styleSheetCode2" :value="dynamicHopCSSUrl" />
|
||||
|
||||
<input
|
||||
type="hidden"
|
||||
name="sgReceiptResponseURL"
|
||||
value="https://fixmyglasstest.safelite.com/FixMyGlass/Payment/Success.aspx" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="sgDeclineResponseURL"
|
||||
value="https://fixmyglasstest.safelite.com/FixMyGlass/Payment/Cancel.aspx" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="sgErrorResponseURL"
|
||||
value="https://fixmyglasstest.safelite.com/FixMyGlass/Payment/Error.aspx" />
|
||||
<input type="hidden" name="sgReceiptResponseURL" :value="piaResponseUrl" />
|
||||
<input type="hidden" name="sgDeclineResponseURL" :value="piaResponseUrl" />
|
||||
<input type="hidden" name="sgErrorResponseURL" :value="piaResponseUrl" />
|
||||
|
||||
<input type="hidden" name="sgheaderline1" :value="sgHeaderline1" />
|
||||
<input type="hidden" name="sgHeader1subtitle" value="" />
|
||||
|
|
@ -139,20 +128,14 @@
|
|||
<input type="hidden" name="sgWorkOrder" :value="workOrderNumber" />
|
||||
<input type="hidden" name="sgEmailAddress" :value="emailAddress" />
|
||||
<input type="hidden" name="paypalInvoiceNumber" :value="invoiceNumber" />
|
||||
<input type="hidden" name="payPalSuccessUrl" :value="payPalSuccessUrl" />
|
||||
<input type="hidden" name="paypalCancelUrl" :value="payPalCancelUrl" />
|
||||
<input type="hidden" name="paypalSuccessUrl" :value="piaResponseUrl" />
|
||||
<input type="hidden" name="paypalCancelUrl" :value="piaCancelUrl" />
|
||||
|
||||
<input
|
||||
type="hidden"
|
||||
name="sgCCDeclineURL"
|
||||
value="https://fixmyglasstest.safelite.com/FixMyGlass/Payment.aspx?error=ccDecline" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="sgCCTimeoutURL"
|
||||
value="https://fixmyglasstest.safelite.com/FixMyGlass/Payment.aspx?error=ccTimeout" />
|
||||
<input type="hidden" name="sgCCDeclineURL" :value="piaCancelUrl" />
|
||||
<input type="hidden" name="sgCCTimeoutURL" :value="piaCancelUrl" />
|
||||
|
||||
<input type="hidden" name="sgTransactionType" value="authorization" />
|
||||
<input type="hidden" name="amount" :value="amountDue" />
|
||||
<input type="hidden" name="amount" :value="totalAmount" />
|
||||
<input type="hidden" name="ctu" :value="ctu" />
|
||||
<input type="hidden" name="orderNumber" :value="workOrderNumber" />
|
||||
|
||||
|
|
@ -177,7 +160,6 @@
|
|||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
|
|
@ -186,6 +168,7 @@ import { externalUrls } from "@/router/router-constants/externalUrl-values";
|
|||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import { applicationConfig } from "@/constants/application-config";
|
||||
import { paymentMethods } from "@/constants/payment-method-constants";
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
|
||||
// Validation
|
||||
|
|
@ -196,8 +179,8 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
checkoutUrl: externalUrls.SAFELITE_HOP,
|
||||
payPalSuccessUrl: applicationConfig.PAYPAL_SUCCESS_URL,
|
||||
payPalCancelUrl: applicationConfig.PAYPAL_CANCEL_URL,
|
||||
piaResponseUrl: applicationConfig.PIA_RESPONSE_URL,
|
||||
piaCancelUrl: applicationConfig.PIA_CANCEL_URL,
|
||||
paymentType: this.getPaymentType(),
|
||||
authToken: "",
|
||||
authSignature: "",
|
||||
|
|
@ -244,6 +227,16 @@ export default {
|
|||
vm.fetchSignatureInfo();
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
dynamicCSSUrl() {
|
||||
const { protocol, hostname, port } = window.location;
|
||||
return `${protocol}//${hostname}:${port}/fmg/css/site-2020Styling.css`;
|
||||
},
|
||||
dynamicHopCSSUrl() {
|
||||
const { protocol, hostname, port } = window.location;
|
||||
return `${protocol}//${hostname}:${port}/fmg/css/hop-styling.css`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
// TODO
|
||||
|
|
@ -257,7 +250,7 @@ export default {
|
|||
|
||||
// no work order, set pia error and return
|
||||
this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, "NO WORK ORDER", false);
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
this.backButtonAction();
|
||||
},
|
||||
getInvoiceNumber() {
|
||||
if (store.getters.order.workOrderNumber) {
|
||||
|
|
@ -266,7 +259,7 @@ export default {
|
|||
|
||||
// no work order, set pia error and return
|
||||
this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, "NO WORK ORDER", false);
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
this.backButtonAction();
|
||||
},
|
||||
getAddress1() {
|
||||
return store.getters.order.serviceLocation.address
|
||||
|
|
@ -294,7 +287,16 @@ export default {
|
|||
: store.getters.order.serviceLocation.provider.address.zipCode;
|
||||
},
|
||||
getPaymentType() {
|
||||
return store.getters.order.payment.payNowType;
|
||||
switch (store.getters.order.payment.piaType) {
|
||||
case paymentMethods.AFTERPAY:
|
||||
return "ap";
|
||||
case paymentMethods.CREDIT_CARD:
|
||||
return "cc";
|
||||
case paymentMethods.PAYPAL:
|
||||
return "pp";
|
||||
default:
|
||||
return store.getters.payment.piaType;
|
||||
}
|
||||
},
|
||||
getHeaderLine1() {
|
||||
if (!this.isPaypal()) {
|
||||
|
|
@ -324,10 +326,12 @@ export default {
|
|||
return itemsForPia;
|
||||
},
|
||||
getAmountDue() {
|
||||
return baseMixin.methods.getAmountDue(store.getters.order.lineItems);
|
||||
return 350.0;
|
||||
//return baseMixin.methods.getAmountDue(store.getters.order.lineItems);
|
||||
},
|
||||
getDisplayAmountDue() {
|
||||
return baseMixin.methods.getDisplayAmountDue(store.getters.order.lineItems);
|
||||
return "$350.00";
|
||||
//return baseMixin.methods.getDisplayAmountDue(store.getters.order.lineItems);
|
||||
},
|
||||
isPaypal() {
|
||||
if (this.getPaymentType() == "pp") {
|
||||
|
|
@ -367,7 +371,6 @@ export default {
|
|||
components: {
|
||||
funnelHeader,
|
||||
navbar,
|
||||
funnelSubHeader,
|
||||
Form,
|
||||
loadingModal,
|
||||
},
|
||||
|
|
@ -375,13 +378,9 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.payment-iframe {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hop-iframe {
|
||||
position: relative;
|
||||
min-height: 1300px;
|
||||
min-height: 1350px;
|
||||
width: 100%;
|
||||
border: 0 none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
isPrimary
|
||||
:buttonText="forwardButtonText"
|
||||
loaderColor="white"
|
||||
class="mb-2"
|
||||
class="mb-2 w-100"
|
||||
@click-event="forwardButtonAction" />
|
||||
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
v-for="alert in prefixedAlertReasons"
|
||||
:key="alert.cmsWidgetName"
|
||||
:ref="alert.cmsWidgetName"
|
||||
class="mt-2 mb-3"
|
||||
class="mt-5 mb-4"
|
||||
:cmsWidgetName="alert.cmsWidgetName"
|
||||
alertClass="alert-warning" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-5" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-4" />
|
||||
<template v-if="ChangeShopLink.length">
|
||||
<textBlock
|
||||
cmsWidgetName="ChangeShopLink"
|
||||
|
|
@ -439,16 +439,16 @@ export default {
|
|||
this.appointmentType === AppointmentTypeStrings.MOBILE &&
|
||||
this.selectedTimeSlotInfo?.isPremiumAppointment
|
||||
) {
|
||||
const earlyBirdIndex = supportingItems.findIndex(
|
||||
const premiumFeeIndex = supportingItems.findIndex(
|
||||
(item) => item.partType == PREMIUM_FEE_PART_TYPE
|
||||
);
|
||||
|
||||
if (earlyBirdIndex >= 0) {
|
||||
supportingItems[earlyBirdIndex].laborAmount =
|
||||
if (premiumFeeIndex >= 0) {
|
||||
supportingItems[premiumFeeIndex].laborAmount =
|
||||
this.mobilePremiumAppointmentFee.laborAmount;
|
||||
supportingItems[earlyBirdIndex].selingPrice =
|
||||
this.mobilePremiumAppointmentFee.selingPrice;
|
||||
supportingItems[earlyBirdIndex].kitPrice =
|
||||
supportingItems[premiumFeeIndex].sellingPrice =
|
||||
this.mobilePremiumAppointmentFee.sellingPrice;
|
||||
supportingItems[premiumFeeIndex].kitPrice =
|
||||
this.mobilePremiumAppointmentFee.kitPrice;
|
||||
} else {
|
||||
supportingItems.push(this.mobilePremiumAppointmentFee);
|
||||
|
|
@ -461,12 +461,12 @@ export default {
|
|||
);
|
||||
} else {
|
||||
// if it's not a mobile and/or premium early bird, then make sure we remove any that may have been added
|
||||
const removeEarlyBirdIndex = supportingItems.findIndex(
|
||||
const removePremiumFeeIndex = supportingItems.findIndex(
|
||||
(item) => item.partType == PREMIUM_FEE_PART_TYPE
|
||||
);
|
||||
|
||||
if (removeEarlyBirdIndex >= 0) {
|
||||
supportingItems.splice(removeEarlyBirdIndex, 1);
|
||||
if (removePremiumFeeIndex >= 0) {
|
||||
supportingItems.splice(removePremiumFeeIndex, 1);
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
|
||||
supportingItems,
|
||||
|
|
|
|||
|
|
@ -46,6 +46,18 @@ export async function getServiceabilityDetails(serviceZipCode, lineItems, pageNa
|
|||
return Promise.resolve(serviceabilityDetails);
|
||||
}
|
||||
|
||||
export async function getShopProviderData(serviceZipCode) {
|
||||
const shopProviderData = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_PROVIDERS,
|
||||
{
|
||||
serviceZipCode: serviceZipCode,
|
||||
},
|
||||
"service-location"
|
||||
);
|
||||
|
||||
return Promise.resolve(shopProviderData);
|
||||
}
|
||||
|
||||
export async function getAvailabilityRating(
|
||||
startDate,
|
||||
endDate,
|
||||
|
|
|
|||
|
|
@ -145,9 +145,6 @@ export default {
|
|||
alertInvalidZipWidgetName: String,
|
||||
customComponentId: String,
|
||||
validationRules: String,
|
||||
onZipUpdateCallback: {
|
||||
type: Function,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
mobileLocationLinkPromptText() {
|
||||
|
|
@ -256,10 +253,7 @@ export default {
|
|||
this.$emit("updated-serviceability", serviceabilityDetails.data);
|
||||
this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase);
|
||||
|
||||
if (this.onZipUpdateCallback) {
|
||||
await this.onZipUpdateCallback(serviceZipCode);
|
||||
}
|
||||
// Update the page level model
|
||||
// update the page level model
|
||||
this.$emit("update:modelValue", this.internalModel);
|
||||
|
||||
this.closeModal();
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@ import { getMountOptions } from "@/helpers/unit-test-helper";
|
|||
|
||||
import store from "@/store";
|
||||
import baseMixin from "@/mixins/base-mixin";
|
||||
import {
|
||||
getServiceabilityDetails,
|
||||
Provider,
|
||||
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
|
||||
|
||||
// Define Mocks
|
||||
jest.mock("@/helpers/cms-content-helper", () => ({
|
||||
|
|
@ -21,7 +17,7 @@ jest.mock("@/helpers/cms-content-helper", () => ({
|
|||
|
||||
jest.mock("./classes/provider");
|
||||
|
||||
const mockGetPricedMobileFeePart = (mockServiceZipCode) => {
|
||||
let mockGetPricedMobileFeePart = (mockServiceZipCode) => {
|
||||
let mobileFeePart = {};
|
||||
|
||||
if (mockServiceZipCode === "43235") {
|
||||
|
|
@ -38,7 +34,7 @@ const mockGetPricedMobileFeePart = (mockServiceZipCode) => {
|
|||
return Promise.resolve(mobileFeePart);
|
||||
};
|
||||
|
||||
const mockGetServiceabilityDetails = (mockServiceZipCode) => {
|
||||
let mockGetServiceabilityDetails = (mockServiceZipCode) => {
|
||||
const serviceabilityDetails = {
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: true,
|
||||
|
|
@ -49,6 +45,17 @@ const mockGetServiceabilityDetails = (mockServiceZipCode) => {
|
|||
return Promise.resolve(serviceabilityDetails);
|
||||
};
|
||||
|
||||
let mockGetShopProviderData = (mockServiceZipCode) => {
|
||||
const result = {
|
||||
data: {
|
||||
mobileProviderNumber: "001820",
|
||||
shopProviders: [{}],
|
||||
},
|
||||
};
|
||||
|
||||
return Promise.resolve(result);
|
||||
};
|
||||
|
||||
jest.mock(
|
||||
"@/layouts/service-location/helpers/service-location-helper/service-location-helper",
|
||||
() => ({
|
||||
|
|
@ -58,6 +65,9 @@ jest.mock(
|
|||
getServiceabilityDetails: jest.fn((mockServiceZipCode) => {
|
||||
return mockGetServiceabilityDetails(mockServiceZipCode);
|
||||
}),
|
||||
getShopProviderData: jest.fn((mockServiceZipCode) => {
|
||||
return mockGetShopProviderData(mockServiceZipCode);
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -401,6 +411,9 @@ describe("service-location.vue", () => {
|
|||
const { wrapper } = setupMocks({});
|
||||
|
||||
await wrapper.setData({
|
||||
shopProviderData: {
|
||||
mobileProviderNumber: "001820",
|
||||
},
|
||||
selectedAppointmentType: "Mobile",
|
||||
providerData: { mobileProviderNumber: "01820" },
|
||||
});
|
||||
|
|
@ -453,6 +466,9 @@ describe("service-location.vue", () => {
|
|||
const { wrapper } = setupMocks({});
|
||||
|
||||
await wrapper.setData({
|
||||
shopProviderData: {
|
||||
mobileProviderNumber: "001820",
|
||||
},
|
||||
selectedAppointmentType: "Mobile",
|
||||
providerData: { mobileProviderNumber: "01820" },
|
||||
});
|
||||
|
|
@ -517,6 +533,9 @@ describe("service-location.vue", () => {
|
|||
const { wrapper } = setupMocks({});
|
||||
|
||||
await wrapper.setData({
|
||||
shopProviderData: {
|
||||
mobileProviderNumber: "001820",
|
||||
},
|
||||
selectedAppointmentType: "Mobile",
|
||||
providerData: { mobileProviderNumber: "01820" },
|
||||
});
|
||||
|
|
@ -554,14 +573,13 @@ describe("service-location.vue", () => {
|
|||
describe("should be logical AND when recalibration is defined.", () => {
|
||||
test("T & T => T", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: true,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -582,14 +600,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("T & F => F", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: false,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -610,14 +627,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("F & T => F", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: false,
|
||||
isRecalibrationServiceableInshop: true,
|
||||
isGlassServiceableMobile: false,
|
||||
isRecalibrationServiceableMobile: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -638,14 +654,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("F & F => F", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: false,
|
||||
isRecalibrationServiceableInshop: false,
|
||||
isGlassServiceableMobile: false,
|
||||
isRecalibrationServiceableMobile: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -666,14 +681,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("displayServiceableMobileOnly should be true if mobile is true and inshop is false.", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: false,
|
||||
isRecalibrationServiceableInshop: false,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -695,14 +709,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("displayServiceableMobileOnly should be false if mobile is false.", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: false,
|
||||
isRecalibrationServiceableInshop: false,
|
||||
isGlassServiceableMobile: false,
|
||||
isRecalibrationServiceableMobile: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -724,14 +737,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("displayServiceableMobileOnly should be false if inShop is true", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: true,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -753,14 +765,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("displayNoShopsAlert should be true if inShop is false and mobile is false", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: false,
|
||||
isRecalibrationServiceableInshop: false,
|
||||
isGlassServiceableMobile: false,
|
||||
isRecalibrationServiceableMobile: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -782,14 +793,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("displayNoShopsAlert should be false if inShop is true and mobile is true", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: true,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -811,14 +821,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("displayNoShopsAlert should be false if inShop is true", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: true,
|
||||
isGlassServiceableMobile: false,
|
||||
isRecalibrationServiceableMobile: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -840,14 +849,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("displayNoShopsAlert should be false if mobile is true", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: false,
|
||||
isRecalibrationServiceableInshop: false,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -869,14 +877,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("displayServiceableInshopOnly should be true if inshop is true and mobile is false", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: true,
|
||||
isGlassServiceableMobile: false,
|
||||
isRecalibrationServiceableMobile: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -898,14 +905,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("displayServiceableInshopOnly should be false if inshop is false", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: false,
|
||||
isRecalibrationServiceableInshop: false,
|
||||
isGlassServiceableMobile: false,
|
||||
isRecalibrationServiceableMobile: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -927,14 +933,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("displayServiceableInshopOnly should be false if mobile is true", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: true,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -956,14 +961,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("displayServiceableInshopOnly should be false in the dual/static recalibration scenario", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: true,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -988,14 +992,13 @@ describe("service-location.vue", () => {
|
|||
describe("should be based only on glass serviceability if recalibration is not defined.", () => {
|
||||
test("Should return true", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: null,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: null,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -1016,14 +1019,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("Should return false", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: false,
|
||||
isRecalibrationServiceableInshop: null,
|
||||
isGlassServiceableMobile: false,
|
||||
isRecalibrationServiceableMobile: null,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -1044,14 +1046,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("displayServiceableMobileOnly should be true if mobile is true and inshop is false.", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: false,
|
||||
isRecalibrationServiceableInshop: null,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: null,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -1073,14 +1074,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("displayServiceableMobileOnly should be false if mobile is false", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: false,
|
||||
isRecalibrationServiceableInshop: null,
|
||||
isGlassServiceableMobile: false,
|
||||
isRecalibrationServiceableMobile: null,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -1102,14 +1102,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("displayServiceableMobileOnly should be false if inShop is true", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: null,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: null,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -1131,14 +1130,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("requiresInshopRecalibration should not be true if recalibration info is null.", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: null,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: null,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -1162,14 +1160,13 @@ describe("service-location.vue", () => {
|
|||
describe("should properly display alerts.", () => {
|
||||
test("Should show mobile-only error if only mobile is available.", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: false,
|
||||
isRecalibrationServiceableInshop: false,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -1191,14 +1188,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("Should not show mobile-only error if not mobile-only.", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: true,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -1220,14 +1216,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("Should show no-shop error if no shops are available.", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: false,
|
||||
isRecalibrationServiceableInshop: false,
|
||||
isGlassServiceableMobile: false,
|
||||
isRecalibrationServiceableMobile: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -1249,14 +1244,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("Should not show no-shop error if shops are available.", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: true,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -1278,14 +1272,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("Should show inshop-only error if only inshop is available", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: true,
|
||||
isGlassServiceableMobile: false,
|
||||
isRecalibrationServiceableMobile: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -1307,14 +1300,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("Should not show inshop-only error if not inshop-only", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: false,
|
||||
isRecalibrationServiceableInshop: false,
|
||||
isGlassServiceableMobile: false,
|
||||
isRecalibrationServiceableMobile: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -1336,14 +1328,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("Should not show inshop-only error if dual or static recalibration, but should show that error instead.", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: true,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
@ -1367,14 +1358,13 @@ describe("service-location.vue", () => {
|
|||
|
||||
test("Should not show dual/static recalibration error if not those recalibration types", async () => {
|
||||
// Arrange
|
||||
getServiceabilityDetails.mockImplementation(() =>
|
||||
mockGetServiceabilityDetails = () =>
|
||||
Promise.resolve({
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: true,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-5" />
|
||||
|
||||
|
|
@ -19,8 +19,7 @@
|
|||
@updated-serviceability="setServiceabilityDetails"
|
||||
@updated-contains-military-base="setContainsMilitaryBase"
|
||||
linkWidgetName="ServiceZipLinkWidget"
|
||||
modalWidgetName="ServiceZipModalWidget"
|
||||
:onZipUpdateCallback="reloadShopData" />
|
||||
modalWidgetName="ServiceZipModalWidget" />
|
||||
|
||||
<alert
|
||||
ref="alertMilitaryBaseZip"
|
||||
|
|
@ -59,7 +58,7 @@
|
|||
alertClass="alert-warning" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center appointment-type">
|
||||
<div class="row justify-content-center appointment-type">
|
||||
<div class="col-md-6">
|
||||
<appointmentTypeQuestion
|
||||
v-model="selectedAppointmentType"
|
||||
|
|
@ -73,7 +72,7 @@
|
|||
validationRules="option-required" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<mobileLocationModalQuestions
|
||||
customComponentId="mobileLocationQuestions"
|
||||
|
|
@ -86,16 +85,15 @@
|
|||
validationRules="mobile-location-required"
|
||||
ref="mobileLocationQuestions"
|
||||
linkWidgetName="MobileLocationLinkWidget"
|
||||
modalWidgetName="MobileLocationModalWidget"
|
||||
:onZipUpdateCallback="reloadShopData" />
|
||||
modalWidgetName="MobileLocationModalWidget" />
|
||||
|
||||
<shopQuestion
|
||||
ref="shopQuestion"
|
||||
v-show="isShopQuestionDisplayed"
|
||||
v-model="selectedProvider"
|
||||
:selectedAppointmentType="selectedAppointmentType"
|
||||
:shopProviderData="shopProviderData"
|
||||
:isDisplayed="isShopQuestionDisplayed"
|
||||
@updated-shop-list="setUpdatedShopList"
|
||||
cmsWidgetName="ShopQuestionWidget" />
|
||||
|
||||
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
|
||||
|
|
@ -135,6 +133,7 @@ import { settleAllPromises } from "@/helpers/layout-helper";
|
|||
import {
|
||||
getPricedMobileFeePart,
|
||||
getServiceabilityDetails,
|
||||
getShopProviderData,
|
||||
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
|
||||
|
||||
import { Provider } from "@/layouts/service-location/classes/provider";
|
||||
|
|
@ -180,7 +179,7 @@ export default {
|
|||
mobileFeePart: null,
|
||||
zipContainsMilitaryBase: false,
|
||||
zipCodeCtu: null,
|
||||
providerData: null,
|
||||
shopProviderData: null,
|
||||
};
|
||||
},
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
|
|
@ -188,7 +187,10 @@ export default {
|
|||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||
|
||||
const serviceZipCode = store.getters.order.serviceLocation.zipCode;
|
||||
const getZipCodeData = baseMixin.methods.getZipCodeData(serviceZipCode, "service-location");
|
||||
const zipCodeDataPromise = baseMixin.methods.getZipCodeData(
|
||||
serviceZipCode,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
const serviceabilityDetailsPromise = getServiceabilityDetails(
|
||||
serviceZipCode,
|
||||
|
|
@ -198,7 +200,7 @@ export default {
|
|||
|
||||
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode, "service-location");
|
||||
|
||||
const shopQuestionInitialDataPromise = shopQuestion.methods.loadInitialData(serviceZipCode);
|
||||
const shopProviderDataPromise = getShopProviderData(serviceZipCode); // shopQuestion.methods.loadInitialData(serviceZipCode);
|
||||
|
||||
// Settle promises and get results
|
||||
const promiseResultMap = [
|
||||
|
|
@ -206,6 +208,10 @@ export default {
|
|||
resultKey: "cmsContent",
|
||||
promise: cmsContentPromise,
|
||||
},
|
||||
{
|
||||
resultKey: "zipCodeData",
|
||||
promise: zipCodeDataPromise,
|
||||
},
|
||||
{
|
||||
resultKey: "mobileFeePart",
|
||||
promise: mobileFeePartPromise,
|
||||
|
|
@ -215,12 +221,8 @@ export default {
|
|||
promise: serviceabilityDetailsPromise,
|
||||
},
|
||||
{
|
||||
resultKey: "zipCodeData",
|
||||
promise: getZipCodeData,
|
||||
},
|
||||
{
|
||||
resultKey: "shopQuestionInitialData",
|
||||
promise: shopQuestionInitialDataPromise,
|
||||
resultKey: "shopProviderData",
|
||||
promise: shopProviderDataPromise,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -232,14 +234,9 @@ export default {
|
|||
vm.setData(
|
||||
resultMap.zipCodeData,
|
||||
resultMap.serviceabilityDetails,
|
||||
resultMap.mobileFeePart
|
||||
resultMap.mobileFeePart,
|
||||
resultMap.shopProviderData
|
||||
);
|
||||
|
||||
// Initialize the Shop Question component
|
||||
vm.$refs.shopQuestion.initializeComponent(resultMap.shopQuestionInitialData);
|
||||
|
||||
// Initialize the page level shop data
|
||||
vm.providerData = resultMap.shopQuestionInitialData;
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -254,7 +251,7 @@ export default {
|
|||
if (newValue.zipCode !== this.zipCode) {
|
||||
this.resetMobileLocation();
|
||||
this.selectedAppointmentType = null;
|
||||
this.selectedProvider = null;
|
||||
this.selectedProvider = new Provider();
|
||||
}
|
||||
|
||||
this.state = newValue.state;
|
||||
|
|
@ -277,20 +274,16 @@ export default {
|
|||
};
|
||||
},
|
||||
set: function (newValue) {
|
||||
this.streetAddress = newValue.addressQuestions.streetAddress;
|
||||
this.apartmentNumberOrBusinessName =
|
||||
newValue.addressQuestions.apartmentNumberOrBusinessName;
|
||||
this.city = newValue.addressQuestions.city;
|
||||
this.state = newValue.addressQuestions.state;
|
||||
this.zipCode = newValue.addressQuestions.zipCode;
|
||||
this.isVehicleProtected = newValue.isVehicleProtected;
|
||||
|
||||
if (newValue.zipCode !== this.zipCode) {
|
||||
if (!this.selectedAppointmentType == "Mobile") {
|
||||
this.selectedAppointmentType = null;
|
||||
}
|
||||
this.selectedProvider = null;
|
||||
if (newValue.addressQuestions.zipCode !== this.zipCode) {
|
||||
getShopProviderData(newValue.addressQuestions.zipCode).then((result) => {
|
||||
this.shopProviderData = result.data;
|
||||
this.selectedProvider = new Provider(
|
||||
this.shopProviderData.mobileProviderNumber
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
this.setMobileLocation(newValue);
|
||||
},
|
||||
},
|
||||
isServiceableMobile() {
|
||||
|
|
@ -355,7 +348,7 @@ export default {
|
|||
store.getters.payment.isInsurance !== null
|
||||
);
|
||||
},
|
||||
setData(zipCodeData, serviceabilityDetails, mobileFeePart) {
|
||||
setData(zipCodeData, serviceabilityDetails, mobileFeePart, shopProviderData) {
|
||||
if (zipCodeData) {
|
||||
this.zipContainsMilitaryBase = zipCodeData.containsMilitaryBase;
|
||||
this.zipCodeCtu = zipCodeData.zipCodeCtu;
|
||||
|
|
@ -368,6 +361,10 @@ export default {
|
|||
if (mobileFeePart) {
|
||||
this.mobileFeePart = mobileFeePart;
|
||||
}
|
||||
|
||||
if (shopProviderData) {
|
||||
this.shopProviderData = shopProviderData;
|
||||
}
|
||||
},
|
||||
setContainsMilitaryBase(val) {
|
||||
if (this.zipContainsMilitaryBase !== val) {
|
||||
|
|
@ -416,12 +413,18 @@ export default {
|
|||
this.isRecalibrationServiceableMobile =
|
||||
serviceabilityDetails.isRecalibrationServiceableMobile;
|
||||
},
|
||||
setMobileLocation(mobileLocation) {
|
||||
this.streetAddress = mobileLocation.addressQuestions.streetAddress;
|
||||
this.apartmentNumberOrBusinessName =
|
||||
mobileLocation.addressQuestions.apartmentNumberOrBusinessName;
|
||||
this.city = mobileLocation.addressQuestions.city;
|
||||
this.state = mobileLocation.addressQuestions.state;
|
||||
this.zipCode = mobileLocation.addressQuestions.zipCode;
|
||||
this.isVehicleProtected = mobileLocation.isVehicleProtected;
|
||||
},
|
||||
async reloadShopData(zipCode) {
|
||||
await this.$refs.shopQuestion.reloadShopData(zipCode);
|
||||
},
|
||||
setUpdatedShopList(providerData) {
|
||||
this.providerData = providerData;
|
||||
},
|
||||
openRecalibrationInformationModal() {
|
||||
this.recalibrationInformationModal.openModal();
|
||||
},
|
||||
|
|
@ -438,7 +441,7 @@ export default {
|
|||
// If it already exists, update the price with latest data
|
||||
if (mobileFeeIndex >= 0) {
|
||||
supportingItems[mobileFeeIndex].laborAmount = this.mobileFeePart.laborAmount;
|
||||
supportingItems[mobileFeeIndex].selingPrice = this.mobileFeePart.selingPrice;
|
||||
supportingItems[mobileFeeIndex].sellingPrice = this.mobileFeePart.sellingPrice;
|
||||
supportingItems[mobileFeeIndex].kitPrice = this.mobileFeePart.kitPrice;
|
||||
} else {
|
||||
supportingItems.push(this.mobileFeePart);
|
||||
|
|
@ -494,21 +497,31 @@ export default {
|
|||
this.updateAndSaveSupportingItems();
|
||||
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
|
||||
},
|
||||
openModalAction(modalName) {
|
||||
this.$refs[modalName].openModal();
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
zipCode: {
|
||||
handler(newValue) {
|
||||
getShopProviderData(this.zipCode).then(async (result) => {
|
||||
this.shopProviderData = result.data;
|
||||
if (this.selectedAppointmentType === "Mobile") {
|
||||
this.selectedProvider = new Provider(
|
||||
this.shopProviderData.mobileProviderNumber
|
||||
);
|
||||
} else {
|
||||
this.selectedProvider = new Provider();
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
selectedAppointmentType: {
|
||||
handler(newValue) {
|
||||
if (newValue === "Mobile") {
|
||||
this.selectedProvider = new Provider(this.providerData.mobileProviderNumber);
|
||||
} else {
|
||||
this.selectedProvider = new Provider();
|
||||
}
|
||||
},
|
||||
},
|
||||
providerData: {
|
||||
handler(newValue) {
|
||||
if (newValue === "Mobile") {
|
||||
this.selectedProvider = new Provider(this.providerData.mobileProviderNumber);
|
||||
this.selectedProvider = new Provider(
|
||||
this.shopProviderData.mobileProviderNumber
|
||||
);
|
||||
} else {
|
||||
this.selectedProvider = new Provider();
|
||||
}
|
||||
|
|
@ -535,7 +548,6 @@ export default {
|
|||
.appointment-type-question {
|
||||
.button-question {
|
||||
.col {
|
||||
padding: 0 0.5rem;
|
||||
@include media-breakpoint-up(md) {
|
||||
padding: 0 0.75rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,9 +68,6 @@ export default {
|
|||
},
|
||||
linkWidgetName: String,
|
||||
modalWidgetName: String,
|
||||
onZipUpdateCallback: {
|
||||
type: Function,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
serviceZipLinkText() {
|
||||
|
|
@ -164,13 +161,9 @@ export default {
|
|||
this.$emit("updated-serviceability", serviceabilityDetails.data);
|
||||
this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase);
|
||||
|
||||
// Update the page level model
|
||||
// update the page level model
|
||||
this.$emit("update:modelValue", this.internalModel);
|
||||
|
||||
if (this.onZipUpdateCallback) {
|
||||
await this.onZipUpdateCallback(serviceZipCode);
|
||||
}
|
||||
|
||||
this.closeModal();
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ const mockCmsContent = {
|
|||
|
||||
const cmsWidgetName = "ShopQuestionWidget";
|
||||
const shopQuestionInitialData = {
|
||||
mobileProviderNumber: "001820",
|
||||
shopProviders: [
|
||||
{
|
||||
address: {
|
||||
|
|
@ -162,15 +163,18 @@ describe("shop-question.vue", () => {
|
|||
selectedAppointmentType: "Dropoff",
|
||||
cmsWidgetName: cmsWidgetName,
|
||||
isDisplayed: true,
|
||||
shopProviderData: null,
|
||||
},
|
||||
mountOptions: {
|
||||
attachTo: document.body,
|
||||
},
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.initializeComponent(shopQuestionInitialData);
|
||||
await wrapper.setProps({
|
||||
shopProviderData: shopQuestionInitialData,
|
||||
});
|
||||
|
||||
// Act
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
|
|
@ -199,47 +203,6 @@ describe("shop-question.vue", () => {
|
|||
});
|
||||
|
||||
it("Should display the 'Show more locations' link when there are more than three locations to chose from", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modelValue: {
|
||||
address: {
|
||||
city: "POWELL",
|
||||
country: "US",
|
||||
state: "OH",
|
||||
streetAddress: "3938 POWELL RD",
|
||||
zipCode: "43065",
|
||||
},
|
||||
distanceInMiles: 16.2690495685233,
|
||||
providerNumber: "003341",
|
||||
},
|
||||
serviceZipCode: "43081",
|
||||
selectedAppointmentType: "Dropoff",
|
||||
cmsWidgetName: cmsWidgetName,
|
||||
isDisplayed: true,
|
||||
},
|
||||
mountOptions: {
|
||||
attachTo: document.body,
|
||||
},
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.initializeComponent(shopQuestionInitialData);
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" });
|
||||
|
||||
// Assert
|
||||
expect(showMoreShopsLink.exists()).toBe(true);
|
||||
expect(showMoreShopsLink.isVisible()).toBe(true);
|
||||
});
|
||||
|
||||
it("Should not display the 'Show more locations' link when there are fewer than three locations to chose from", async () => {
|
||||
// Arrange
|
||||
const alsoShopQuestionInitialData = {
|
||||
shopProviders: [
|
||||
|
|
@ -276,6 +239,17 @@ describe("shop-question.vue", () => {
|
|||
distanceInMiles: 11.738869544543,
|
||||
providerNumber: "003343",
|
||||
},
|
||||
{
|
||||
address: {
|
||||
city: "COLUMBUS",
|
||||
country: "US",
|
||||
state: "OH",
|
||||
streetAddress: "1670 HARMON AVE",
|
||||
zipCode: "43223",
|
||||
},
|
||||
distanceInMiles: 15.9727889297435,
|
||||
providerNumber: "006747",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -297,6 +271,7 @@ describe("shop-question.vue", () => {
|
|||
selectedAppointmentType: "Dropoff",
|
||||
cmsWidgetName: cmsWidgetName,
|
||||
isDisplayed: true,
|
||||
shopProviderData: null,
|
||||
},
|
||||
mountOptions: {
|
||||
attachTo: document.body,
|
||||
|
|
@ -304,17 +279,94 @@ describe("shop-question.vue", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.initializeComponent(alsoShopQuestionInitialData);
|
||||
await wrapper.setProps({
|
||||
shopProviderData: alsoShopQuestionInitialData,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" });
|
||||
|
||||
// Assert
|
||||
expect(showMoreShopsLink.exists()).toBe(false);
|
||||
expect(showMoreShopsLink.exists()).toBeTruthy();
|
||||
expect(showMoreShopsLink.isVisible()).toBeTruthy();
|
||||
});
|
||||
|
||||
it("Should display the next three shops when the 'Show more location' link is clicked", async () => {
|
||||
it("Should not display the 'Show more locations' link when there are fewer than three locations to chose from", async () => {
|
||||
// Arrange
|
||||
const alsoShopQuestionInitialData = {
|
||||
shopProviders: [
|
||||
{
|
||||
address: {
|
||||
city: "WESTERVILLE",
|
||||
country: "US",
|
||||
state: "OH",
|
||||
streetAddress: "4403 EXECUTIVE PKWY",
|
||||
zipCode: "43081",
|
||||
},
|
||||
distanceInMiles: 5.16769294095201,
|
||||
providerNumber: "003335",
|
||||
},
|
||||
{
|
||||
address: {
|
||||
city: "WORTHINGTON",
|
||||
country: "US",
|
||||
state: "OH",
|
||||
streetAddress: "760 DEARBORN PARK LN",
|
||||
zipCode: "43085",
|
||||
},
|
||||
distanceInMiles: 10.5865432478478,
|
||||
providerNumber: "001820",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modelValue: {
|
||||
address: {
|
||||
city: "POWELL",
|
||||
country: "US",
|
||||
state: "OH",
|
||||
streetAddress: "3938 POWELL RD",
|
||||
zipCode: "43065",
|
||||
},
|
||||
distanceInMiles: 16.2690495685233,
|
||||
providerNumber: "003341",
|
||||
},
|
||||
serviceZipCode: "43081",
|
||||
selectedAppointmentType: "Dropoff",
|
||||
cmsWidgetName: cmsWidgetName,
|
||||
isDisplayed: true,
|
||||
shopProviderData: null,
|
||||
},
|
||||
mountOptions: {
|
||||
attachTo: document.body,
|
||||
},
|
||||
});
|
||||
|
||||
// Act
|
||||
await wrapper.setProps({
|
||||
shopProviderData: alsoShopQuestionInitialData,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" });
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
// Assert
|
||||
expect(showMoreShopsLink.exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
//TODO: Add test to ensure the "Show more locations" link is not visible when all the existing shops are displayed.
|
||||
|
||||
it("Should display the next three shops when the 'Show more locations' link is clicked", async () => {
|
||||
// Arrange
|
||||
const displayedAnswers = [
|
||||
{
|
||||
|
|
@ -355,6 +407,7 @@ describe("shop-question.vue", () => {
|
|||
selectedAppointmentType: "Dropoff",
|
||||
cmsWidgetName: cmsWidgetName,
|
||||
isDisplayed: true,
|
||||
shopProviderData: null,
|
||||
},
|
||||
mountOptions: {
|
||||
attachTo: document.body,
|
||||
|
|
@ -362,7 +415,9 @@ describe("shop-question.vue", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.initializeComponent(shopQuestionInitialData);
|
||||
await wrapper.setProps({
|
||||
shopProviderData: shopQuestionInitialData,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
|
|
@ -384,7 +439,7 @@ describe("shop-question.vue", () => {
|
|||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
showMoreShopsLink.trigger("click");
|
||||
showMoreShopsLink.trigger("click-event");
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
|
|
@ -443,6 +498,7 @@ describe("shop-question.vue", () => {
|
|||
serviceZipCode: "43081",
|
||||
selectedAppointmentType: "Dropoff",
|
||||
cmsWidgetName: cmsWidgetName,
|
||||
shopProviderData: null,
|
||||
},
|
||||
mountOptions: {
|
||||
attachTo: document.body,
|
||||
|
|
@ -450,7 +506,9 @@ describe("shop-question.vue", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.initializeComponent(shopQuestionInitialData);
|
||||
await wrapper.setProps({
|
||||
shopProviderData: shopQuestionInitialData,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
|
@ -468,15 +526,18 @@ describe("shop-question.vue", () => {
|
|||
modelValue: null,
|
||||
selectedAppointmentType: "Dropoff",
|
||||
cmsWidgetName: cmsWidgetName,
|
||||
shopProviderData: null,
|
||||
},
|
||||
mountOptions: {
|
||||
attachTo: document.body,
|
||||
},
|
||||
});
|
||||
|
||||
// Act
|
||||
await wrapper.vm.initializeComponent(shopQuestionInitialData);
|
||||
await wrapper.setProps({
|
||||
shopProviderData: shopQuestionInitialData,
|
||||
});
|
||||
|
||||
// Act
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.vm.answers.length).toEqual(3);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
linkType="text"
|
||||
:text="showMoreShopsLinkText"
|
||||
href="#!"
|
||||
@click-event="getNextShopsFromList"
|
||||
@click-event="getNextShopsFromList(3)"
|
||||
:aria-label="showMoreShopsLinkText" />
|
||||
</div>
|
||||
</transition>
|
||||
|
|
@ -43,8 +43,6 @@ import buttonQuestion from "@/digital-components/button-question/button-question
|
|||
import shopListButton from "./shop-list-button/shop-list-button";
|
||||
import textLink from "@/ux-components/text-link/text-link";
|
||||
|
||||
import { storeActions } from "@/constants/store-actions.js";
|
||||
|
||||
// Supporting files
|
||||
import { defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
|
|
@ -63,7 +61,6 @@ export default {
|
|||
mixins: [baseMixin],
|
||||
data() {
|
||||
return {
|
||||
shopProviders: [],
|
||||
shopListButton: shopListButton,
|
||||
answers: [],
|
||||
shopIndex: 0,
|
||||
|
|
@ -76,6 +73,7 @@ export default {
|
|||
default: () => null,
|
||||
},
|
||||
selectedAppointmentType: String,
|
||||
shopProviderData: Object,
|
||||
cmsWidgetName: String,
|
||||
validationRules: String,
|
||||
isDisplayed: Boolean,
|
||||
|
|
@ -84,6 +82,9 @@ export default {
|
|||
questionText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "QuestionText");
|
||||
},
|
||||
shopProviders() {
|
||||
return this.shopProviderData?.shopProviders ?? [];
|
||||
},
|
||||
selectedValue: {
|
||||
get: function () {
|
||||
return this.modelValue;
|
||||
|
|
@ -124,22 +125,6 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
loadInitialData(serviceZipCode) {
|
||||
return this.loadData(serviceZipCode);
|
||||
},
|
||||
loadData(serviceZipCode) {
|
||||
return baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_PROVIDERS,
|
||||
{
|
||||
serviceZipCode: serviceZipCode,
|
||||
},
|
||||
"service-location"
|
||||
);
|
||||
},
|
||||
initializeComponent(shopQuestionInitialData) {
|
||||
this.shopProviders = shopQuestionInitialData.shopProviders;
|
||||
this.$emit("updated-shop-list", shopQuestionInitialData);
|
||||
},
|
||||
async getNextShopsFromList(numberToGet = 3) {
|
||||
const shopIterator = (array, n) => {
|
||||
const l = array.length;
|
||||
|
|
@ -199,15 +184,6 @@ export default {
|
|||
this.answers = [];
|
||||
this.shopIndex = 0;
|
||||
},
|
||||
async reloadShopData(serviceZipCode) {
|
||||
const result = await this.loadData(serviceZipCode);
|
||||
this.initializeComponent(result.data);
|
||||
|
||||
this.resetAnswers();
|
||||
|
||||
await nextTick();
|
||||
await this.getNextShopsFromList();
|
||||
},
|
||||
getSelectedProviderObject(providerNumber) {
|
||||
const provider =
|
||||
this.shopProviders?.find((provider) => provider.providerNumber == providerNumber) ??
|
||||
|
|
@ -238,20 +214,19 @@ export default {
|
|||
},
|
||||
shopProviders: {
|
||||
async handler(newValue) {
|
||||
this.resetAnswers();
|
||||
|
||||
await nextTick();
|
||||
const selectedShopIndex = this.getSelectedProviderIndex(
|
||||
newValue,
|
||||
this.selectedProviderNumber
|
||||
);
|
||||
|
||||
if (this.selectedAppointmentType) {
|
||||
const selectedShopIndex = this.getSelectedProviderIndex(
|
||||
newValue,
|
||||
this.selectedProviderNumber
|
||||
);
|
||||
|
||||
if (selectedShopIndex >= 3) {
|
||||
await this.getNextShopsFromList(selectedShopIndex + 1);
|
||||
} else {
|
||||
await this.getNextShopsFromList();
|
||||
await nextTick();
|
||||
}
|
||||
if (selectedShopIndex >= 3) {
|
||||
await this.getNextShopsFromList(selectedShopIndex + 1);
|
||||
} else {
|
||||
await this.getNextShopsFromList();
|
||||
await nextTick();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
:isDismissible="false" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<damageLocationQuestion
|
||||
ref="damageLocation"
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
groupName="DamageLocationQuestion" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<windshieldOptions
|
||||
class="windshield-options"
|
||||
|
|
@ -66,7 +66,7 @@
|
|||
validationRules="replace-options-required" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
|
|
@ -551,9 +551,19 @@ export default {
|
|||
|
||||
<style lang="scss">
|
||||
.replace-options-question {
|
||||
.question-text {
|
||||
margin: 1rem 0 0 0;
|
||||
@include media-breakpoint-up(md) {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
}
|
||||
.two-list-card-width {
|
||||
width: 100%;
|
||||
flex: 0 auto;
|
||||
margin-top: 0.5rem;
|
||||
&:first-child {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@
|
|||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
ref="vehicleBanner"
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
|
|
@ -17,20 +17,18 @@
|
|||
<funnelSubHeader
|
||||
ref="funnelSubHeader"
|
||||
cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="prevent-squish my-5">
|
||||
<div class="row">
|
||||
<div class="col pb-0">
|
||||
<alert
|
||||
class="rounded border-0 shadow-sm"
|
||||
alertClass="alert-warning"
|
||||
cmsWidgetName="AlertWidget"
|
||||
:isDismissible="false" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4 my-5">
|
||||
<alert
|
||||
class="rounded border-0 shadow-sm"
|
||||
alertClass="alert-warning"
|
||||
cmsWidgetName="AlertWidget"
|
||||
:isDismissible="false" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<div v-for="(item, i) in PartsOrQuestions" :key="i">
|
||||
<!-- Render horizontal lines if there is multi-glass (aka if i > 0) -->
|
||||
|
|
|
|||
|
|
@ -5,12 +5,19 @@ import vehicle from "@/layouts/vehicle/vehicle.vue";
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import { nextTick } from "vue";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import baseMixin from "../../mixins/base-mixin";
|
||||
|
||||
// Mock our module for promises.
|
||||
jest.mock("@/helpers/layout-helper.js", () => ({
|
||||
settleAllPromises: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock fetchCmsContentForPage
|
||||
jest.mock("@/helpers/cms-content-helper", () => ({
|
||||
fetchCmsContentForPage: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("vehicle.vue", () => {
|
||||
test("arePagePrerequisitesValid should be true ", async () => {
|
||||
//Arrange
|
||||
|
|
@ -40,17 +47,24 @@ describe("vehicle.vue", () => {
|
|||
});
|
||||
|
||||
function setupMocks() {
|
||||
const wrapper = shallowMount(
|
||||
vehicle,
|
||||
getMountOptions({
|
||||
router: {
|
||||
navigate: jest.fn(),
|
||||
navigate: jest.fn(),
|
||||
navigateWithSaving: jest.fn(),
|
||||
navigateWithoutSaving: jest.fn(),
|
||||
},
|
||||
})
|
||||
);
|
||||
const mountOptions = getMountOptions({
|
||||
router: {
|
||||
navigate: jest.fn(),
|
||||
navigate: jest.fn(),
|
||||
navigateWithSaving: jest.fn(),
|
||||
navigateWithoutSaving: jest.fn(),
|
||||
},
|
||||
});
|
||||
|
||||
//Mock props
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
mountOptions.mixins = [mockMixin];
|
||||
const wrapper = shallowMount(vehicle, mountOptions);
|
||||
|
||||
return { wrapper };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@
|
|||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="siteSubHeader" />
|
||||
<div
|
||||
cmsWidgetName="FindYourVehicleCopyWidget"
|
||||
v-html="this.findYourVehicleCopy"
|
||||
class="mt-2 mb-4 small"></div>
|
||||
<vehicleQuestion
|
||||
ref="vehicleYearQuestion"
|
||||
v-model="selectedYear"
|
||||
|
|
@ -294,6 +298,9 @@ export default {
|
|||
else if (this.imageUrl == null && this.carId !== null) return false;
|
||||
else return true;
|
||||
},
|
||||
findYourVehicleCopy() {
|
||||
return this.getCmsContent("FindYourVehicleCopyWidget", "QuestionText");
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getVehicle(year, make, model, style) {
|
||||
|
|
@ -430,6 +437,6 @@ export default {
|
|||
}
|
||||
.siteSubHeader {
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<textboxQuestion
|
||||
class="mb-4 mt-5"
|
||||
class="mb-2 mt-5"
|
||||
cmsWidgetName="VinNumberQuestionWidget"
|
||||
v-model="vin"
|
||||
customInputId="vin"
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ export default {
|
|||
lineItem.partType != partTypeStrings.RAIN_DEFENSE
|
||||
);
|
||||
});
|
||||
|
||||
let totalPrice = this.getTotalPriceOfAllLineItemsAndChildParts(lineItemsToPrice);
|
||||
return totalPrice;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
militaryToTwelveHourTime,
|
||||
getDisplayTextForDurationLength,
|
||||
} from "@/layouts/schedule/helpers/schedule-helper";
|
||||
import { paymentMethods } from "@/constants/payment-method-constants";
|
||||
|
||||
import { getDateDifferenceInDays } from "@/helpers/date-helper";
|
||||
// Export State
|
||||
|
|
@ -94,9 +95,24 @@ const getDefaultState = () => {
|
|||
coverageStatus: null,
|
||||
},
|
||||
parentAccountNumber: 0,
|
||||
payNowType: "cc",
|
||||
isPia: null,
|
||||
piaType: null,
|
||||
piaErrorCode: null,
|
||||
inactivePromos: null,
|
||||
paypalToken: null,
|
||||
ccToken: {
|
||||
subscriptionId: null,
|
||||
expMonth: null,
|
||||
expYear: null,
|
||||
cardType: null,
|
||||
billToPostalCode: null,
|
||||
billToFirstName: null,
|
||||
billToLastName: null,
|
||||
referenceNumber: null,
|
||||
authCode: null,
|
||||
transactionId: null,
|
||||
transReferenceNumber: null,
|
||||
},
|
||||
},
|
||||
schedule: {
|
||||
date: null,
|
||||
|
|
@ -227,8 +243,11 @@ export const mutations = {
|
|||
updateIsInsurance(state, isInsurance) {
|
||||
state.order.payment.isInsurance = isInsurance;
|
||||
},
|
||||
updatePayNowType(state, payNowType) {
|
||||
state.order.payment.payNowType = payNowType;
|
||||
updateIsPia(state, isPia) {
|
||||
state.order.payment.isPia = isPia;
|
||||
},
|
||||
updatePiaType(state, piaType) {
|
||||
state.order.payment.piaType = piaType;
|
||||
},
|
||||
updatePiaErrorCode(state, piaErrorCode) {
|
||||
state.order.payment.piaErrorCode = piaErrorCode;
|
||||
|
|
@ -239,6 +258,22 @@ export const mutations = {
|
|||
updateWorkOrderNumber(state, workOrderNumber) {
|
||||
state.order.workOrderNumber = workOrderNumber;
|
||||
},
|
||||
updateCCToken(state, ccToken) {
|
||||
state.order.payment.ccToken.subscriptionId = ccToken.subscriptionId;
|
||||
state.order.payment.ccToken.expMonth = ccToken.expMonth;
|
||||
state.order.payment.ccToken.expYear = ccToken.expYear;
|
||||
state.order.payment.ccToken.cardType = ccToken.cardType;
|
||||
state.order.payment.ccToken.billToPostalCode = ccToken.billToPostalCode;
|
||||
state.order.payment.ccToken.billToFirstName = ccToken.billToFirstName;
|
||||
state.order.payment.ccToken.billToLastName = ccToken.billToLastName;
|
||||
state.order.payment.ccToken.referenceNumber = ccToken.referenceNumber;
|
||||
state.order.payment.ccToken.authCode = ccToken.authCode;
|
||||
state.order.payment.ccToken.transactionId = ccToken.transactionId;
|
||||
state.order.payment.ccToken.transReferenceNumber = ccToken.transReferenceNumber;
|
||||
},
|
||||
updatePaypalToken(state, ppToken) {
|
||||
state.order.payment.paypalToken = ppToken;
|
||||
},
|
||||
updateInsuranceVerifiedStatus(state, isVerified) {
|
||||
state.order.payment.insuranceCoverage.isVerified = isVerified;
|
||||
},
|
||||
|
|
@ -418,6 +453,10 @@ export const mutations = {
|
|||
state.order.serviceLocation.state = null;
|
||||
state.order.serviceLocation.isVehicleProtected = null;
|
||||
},
|
||||
resetPaymentMethodChoice(state) {
|
||||
state.order.payment.isPia = null;
|
||||
state.order.payment.piaType = null;
|
||||
},
|
||||
// Misc Mutations
|
||||
updateStateWithOrderInformation(state, sessionInformation) {
|
||||
state.order.referralNumber = sessionInformation.order.referralNumber;
|
||||
|
|
@ -672,7 +711,10 @@ function getTimeSlotsAdditionalEventData(
|
|||
) {
|
||||
var numberOfDays = null;
|
||||
if (firstAvailableAppointmentDateString)
|
||||
numberOfDays = getDateDifferenceInDays(new Date(), firstAvailableAppointmentDateString);
|
||||
numberOfDays = getDateDifferenceInDays(
|
||||
new Date().toISOString().split("T")[0],
|
||||
firstAvailableAppointmentDateString
|
||||
);
|
||||
|
||||
if (shopAppointmentType)
|
||||
return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ShopAppointmentType:${shopAppointmentType},ProvisionalTriggers:${provisionalTriggers.join(
|
||||
|
|
@ -885,6 +927,7 @@ export const actions = {
|
|||
context.commit(storeMutations.RESET_SERVICE_LOCATION_PROVIDER);
|
||||
|
||||
context.commit(storeMutations.RESET_SCHEDULE);
|
||||
context.commit(storeMutations.RESET_PAYMENT_METHOD_CHOICE);
|
||||
},
|
||||
|
||||
resetState(context) {
|
||||
|
|
@ -1550,6 +1593,7 @@ export const actions = {
|
|||
endpoint: endpoints.SaveSession.url,
|
||||
payload: {
|
||||
submitAfterSave: context.state.submitAfterSave,
|
||||
userAgent: navigator.userAgent,
|
||||
applicationUser: {
|
||||
crmCustomerId: applicationUser.crmCustomerId,
|
||||
experiments: applicationUser.experiments,
|
||||
|
|
@ -1604,6 +1648,24 @@ export const actions = {
|
|||
isInsurance: order.payment.isInsurance,
|
||||
parentAccountNumber: order.payment.parentAccountNumber,
|
||||
inactivePromos: order.payment.inactivePromos,
|
||||
isCreditCard:
|
||||
order.payment.piaType == paymentMethods.CREDIT_CARD ? true : false,
|
||||
isPaypal: order.payment.piaType == paymentMethods.PAYPAL ? true : false,
|
||||
isAfterPay: order.payment.piaType == paymentMethods.AFTERPAY ? true : false,
|
||||
paypalToken: order.payment.paypalToken,
|
||||
ccToken: {
|
||||
subscriptionId: order.payment.ccToken.subscriptionId,
|
||||
expMonth: order.payment.ccToken.expMonth,
|
||||
expYear: order.payment.ccToken.expYear,
|
||||
cardType: order.payment.ccToken.cardType,
|
||||
billToPostalCode: order.payment.ccToken.billToPostalCode,
|
||||
billToFirstName: order.payment.ccToken.billToFirstName,
|
||||
billToLastName: order.payment.ccToken.billToLastName,
|
||||
referenceNumber: order.payment.ccToken.referenceNumber,
|
||||
authCode: order.payment.ccToken.authCode,
|
||||
transactionId: order.payment.ccToken.transactionId,
|
||||
transReferenceNumber: order.payment.ccToken.transReferenceNumber,
|
||||
},
|
||||
},
|
||||
serviceLocation: {
|
||||
streetAddress: order.serviceLocation.address,
|
||||
|
|
@ -1721,6 +1783,12 @@ export const actions = {
|
|||
savePiaWorkOrder(context, piaWorkOrder) {
|
||||
context.commit(storeMutations.UPDATE_PIA_WORK_ORDER, piaWorkOrder);
|
||||
},
|
||||
saveCCToken(context, ccToken) {
|
||||
context.commit(storeMutations.UPDATE_CCTOKEN, ccToken);
|
||||
},
|
||||
savePaypalToken(context, ppToken) {
|
||||
context.commit(storeMutations.UPDATE_PAYPAL_TOKEN, ppToken);
|
||||
},
|
||||
|
||||
// Business domain actions
|
||||
|
||||
|
|
@ -2007,11 +2075,19 @@ export const actions = {
|
|||
},
|
||||
|
||||
savePaymentType(context, isInsurance) {
|
||||
if (isInsurance !== context.getters.payment.isInsurance) {
|
||||
context.commit(storeMutations.RESET_PAYMENT_METHOD_CHOICE);
|
||||
}
|
||||
|
||||
context.commit(storeMutations.UPDATE_IS_INSURANCE, isInsurance);
|
||||
},
|
||||
|
||||
savePayNowType(context, payNowType) {
|
||||
context.commit(storeMutations.UPDATE_PAY_NOW_TYPE, payNowType);
|
||||
savePaymentMethodChoice(context, paymentMethod) {
|
||||
const isPia = paymentMethod !== paymentMethods.LATER;
|
||||
|
||||
context.commit(storeMutations.UPDATE_IS_PIA, isPia);
|
||||
|
||||
context.commit(storeMutations.UPDATE_PIA_TYPE, isPia ? paymentMethod : null);
|
||||
},
|
||||
|
||||
saveParentAccountNumber(context, parentAccountNumber) {
|
||||
|
|
@ -2111,14 +2187,13 @@ export const actions = {
|
|||
pageNameToLog,
|
||||
}
|
||||
) {
|
||||
const flattenedLineItemsWithChildParts =
|
||||
getFlattenedArrayOfLineItemsWithChildParts(pricedLineItems);
|
||||
|
||||
const lineItemsWithOnlyPriceInfo = flattenedLineItemsWithChildParts.map((lineItem) => ({
|
||||
// const flattenedLineItemsWithChildParts =
|
||||
// getFlattenedArrayOfLineItemsWithChildParts(pricedLineItems);
|
||||
const lineItemsWithOnlyPriceInfo = pricedLineItems.map((lineItem) => ({
|
||||
partNumber: lineItem.partNumber,
|
||||
laborAmount: lineItem.laborAmount,
|
||||
kitPrice: lineItem.kitPrice,
|
||||
sellingPrice: lineItem.sellingPrice,
|
||||
laborAmount: lineItem.laborAmount ?? 0,
|
||||
kitPrice: lineItem.kitPrice ?? 0,
|
||||
sellingPrice: lineItem.sellingPrice ?? 0,
|
||||
}));
|
||||
|
||||
const pricedLineItemsFormattedForRequest =
|
||||
|
|
@ -2281,6 +2356,7 @@ export const actions = {
|
|||
context.state.order.serviceLocation.appointmentType
|
||||
) {
|
||||
context.commit(storeMutations.RESET_SCHEDULE);
|
||||
context.commit(storeMutations.RESET_PAYMENT_METHOD_CHOICE);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2452,8 +2528,7 @@ function convertGlassPieceNamingFromApi(glassArray) {
|
|||
}
|
||||
|
||||
function addPricesToLineItems(lineItems, pricingLineItems) {
|
||||
const pricedLineItems = deepClone(lineItems);
|
||||
pricedLineItems.forEach((lineItem) => {
|
||||
lineItems.forEach((lineItem) => {
|
||||
let lineItemIndex = pricingLineItems.findIndex(
|
||||
(pricingLineItem) => pricingLineItem.partNumber === lineItem.partNumber
|
||||
);
|
||||
|
|
@ -2466,13 +2541,15 @@ function addPricesToLineItems(lineItems, pricingLineItems) {
|
|||
lineItem.laborAmount = pricedLineItem.laborAmount;
|
||||
lineItem.sellingPrice = pricedLineItem.sellingPrice;
|
||||
lineItem.kitPrice = pricedLineItem.kitPrice;
|
||||
lineItem.salesTax = pricedLineItem.salesTax;
|
||||
});
|
||||
|
||||
return pricedLineItems;
|
||||
return lineItems;
|
||||
}
|
||||
|
||||
function addTaxesToPricedLineItems(lineItems, taxingLineItems) {
|
||||
lineItems.forEach((lineItem) => {
|
||||
const taxedLineItems = deepClone(lineItems);
|
||||
taxedLineItems.forEach((lineItem) => {
|
||||
let lineItemIndex = taxingLineItems.findIndex(
|
||||
(taxingLineItem) => taxingLineItem.partNumber === lineItem.partNumber
|
||||
);
|
||||
|
|
@ -2482,10 +2559,10 @@ function addTaxesToPricedLineItems(lineItems, taxingLineItems) {
|
|||
}
|
||||
|
||||
let taxedLineItem = taxingLineItems.splice(lineItemIndex, 1)[0];
|
||||
lineItem.SalesTax = taxedLineItem.SalesTax;
|
||||
lineItem.salesTax = taxedLineItem.salesTax;
|
||||
});
|
||||
|
||||
return lineItems;
|
||||
return taxedLineItems;
|
||||
}
|
||||
|
||||
function getArrayOfAllLineItems(lineItems) {
|
||||
|
|
|
|||
|
|
@ -753,6 +753,19 @@ describe("Actions", () => {
|
|||
isVerified: false,
|
||||
},
|
||||
isInsurance: false,
|
||||
ccToken: {
|
||||
subscriptionId: null,
|
||||
expMonth: null,
|
||||
expYear: null,
|
||||
cardType: null,
|
||||
billToPostalCode: null,
|
||||
billToFirstName: null,
|
||||
billToLastName: null,
|
||||
referenceNumber: null,
|
||||
authCode: null,
|
||||
transactionId: null,
|
||||
transReferenceNumber: null,
|
||||
},
|
||||
},
|
||||
serviceLocation: {},
|
||||
customer: {},
|
||||
|
|
|
|||
|
|
@ -14,6 +14,30 @@ body {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
// Set max-width on columns to prevent overly-wide
|
||||
// components on extra wide screens.
|
||||
.col-md-6 {
|
||||
max-width: 472px;
|
||||
@include media-breakpoint-up(xl) {
|
||||
max-width: 708px;
|
||||
}
|
||||
.col {
|
||||
max-width: 236px;
|
||||
&.one-list-card-width {
|
||||
max-width: 472px;
|
||||
@include media-breakpoint-up(xl) {
|
||||
max-width: 66.6666666%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.col-xl-4 {
|
||||
max-width: 472px;
|
||||
.col {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
//END set max-width on columns
|
||||
}
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div
|
||||
class="alert fade show text-center mb-0 py-2 px-4"
|
||||
class="alert fade show text-center mb-0 py-2 px-4 border-0"
|
||||
role="alert"
|
||||
:class="[
|
||||
isDismissible ? 'alert-dismissible' : '',
|
||||
|
|
|
|||
Loading…
Reference in a new issue