Merge branch 'develop' into feature/CSR-1384-modals-for-add-vaps

This commit is contained in:
Adam Caouette 2023-10-25 21:45:43 -04:00
commit c75c406c50
33 changed files with 1029 additions and 8536 deletions

13
public/css/README.md Normal file
View file

@ -0,0 +1,13 @@
# Using Sass in this folder
This public/css folder uses a standalone .scss > .css setup.
## Usage
From a Terminal window, run the Sass Watch command on this folder only:
```bash
sass --no-source-map --watch public/css/hop-styling.scss:public/css/hop-styling.css
```
This will output a CSS file that can be consumed by the Payment page.
# Do not use the .css file on its own. You must use the .scss file and have Sass compile it to the .css file.

View file

@ -1,336 +1,194 @@
.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,
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
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;
font-family: Roboto;
font-weight: 400;
color: #4D5151;
margin: 0;
background-color: white;
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 {
body .validation-info {
display: none;
}
.optional-label::after {
body .italicSmall {
font-style: normal;
font-size: 14px;
}
body span {
line-height: 24px;
}
body a {
color: #1574A1;
text-decoration: none;
}
body .form-group {
display: flex;
flex-direction: column;
}
body .headerlinecontainer .headerline1 {
font-size: 20px;
line-height: 32px;
font-weight: 400;
margin: 24px 0;
}
body input,
body select {
margin-top: 4px;
}
body input {
border-radius: 8px;
border: 1px solid #8E9292;
padding: 16px;
}
body input:focus, body input:focus-visible {
box-shadow: 0 0 0 2.5px #1574a1;
border: none;
outline: none;
}
body input:disabled, body input.disabled {
background-color: #f5f5f5;
filter: grayscale(100%);
}
body input:disabled:hover, body input.disabled:hover {
box-shadow: 0 0 0 4px transparent;
border: 1px solid #8e9292;
}
body input:hover {
border: 1px solid #8e9292;
box-shadow: 0 0 0 4px #9fcee6;
}
body select {
border-radius: 8px;
border: 1px solid #8E9292;
padding: 16px;
width: 100%;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%231474a2'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: 95% 50%;
background-size: 15px 9px;
appearance: none;
}
body .creditCardSpecific div {
padding: 8px 0;
}
body .creditCardSpecific div .headerlinecontainer {
padding: 0;
}
body .creditCardSpecific div label {
font-weight: 500;
}
body .creditCardSpecific div:first-child {
display: none;
}
body .creditCardSpecific #infoRow2 {
margin-bottom: 0;
}
body #cardExpirationContainer {
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 0;
}
@media (min-width: 400px) {
body #cardExpirationContainer {
flex-direction: row;
}
body #cardExpirationContainer .card_expirationYear,
body #cardExpirationContainer .card_expirationMonth {
display: flex;
flex-direction: column;
width: 100%;
}
body #cardExpirationContainer .card_expirationYear {
margin-left: 16px;
}
}
body .optional-label span {
display: none;
}
body .optional-label:after {
content: " (optional)";
font-weight: 300;
font-style: normal;
font-size: 15px;
}
.creditCardSpecific {
display: none;
body .buttonContainer {
height: 48px;
}
@media (max-width: 500px) {
.buy-backdrop.afterpay-checkout-wrapper {
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
body .buttonContainer button {
position: relative;
background: linear-gradient(270deg, #1574a1 0%, #003d58 100%);
border: none;
border-radius: 8px;
color: #fff;
justify-content: center;
font-weight: 500;
font-size: 16px;
height: 48px;
}
@media (hover: hover) {
body .buttonContainer button {
background: linear-gradient(270deg, #1574a1 0%, #003d58 100%);
}
}
}
body .buttonContainer button:focus {
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
}
body .buttonContainer button:focus, body .buttonContainer button:focus-visible {
outline: none;
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
color: #fff;
background: linear-gradient(270deg, rgb(6, 87, 124) 0%, rgb(6, 87, 124) 100%);
}
body .buttonContainer button:disabled {
background: #e3e4e4 !important;
background: linear-gradient(270deg, #e3e4e4 0%, #e3e4e4 100%) !important;
color: #4d5151 !important;
font-weight: 400;
height: 48px;
border: none;
border-radius: 8px;
cursor: pointer;
pointer-events: all;
}
body .buttonContainer button.has-loader {
color: #fff;
background: #06577c;
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
pointer-events: auto;
}
body .buttonContainer button.delay {
transition: background 0s 0s ease-in-out;
}
body .has-error input,
body .has-error select {
border: 1px solid #d4281c;
}
body .has-error input:focus,
body .has-error select:focus {
border: 1px solid transparent;
}
body .has-error input:hover,
body .has-error select:hover {
border: 1px solid #d4281c;
box-shadow: 0 0 0 4px #fcbfbb;
}
body .has-error select {
border: 1px solid #d4281c;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%23d4281c'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: 95% 50%;
background-size: 15px 9px;
appearance: none;
}
body .has-error small {
margin-top: 4px;
color: #d4281c;
}
body .cc-error-container #alert-message {
background-color: #fcbfbb;
border: 1px solid #d4281c;
}

220
public/css/hop-styling.scss Normal file
View file

@ -0,0 +1,220 @@
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
body {
font-family: Roboto;
font-weight: 400;
color: #4D5151;
margin: 0;
background-color: white;
padding: 8px;
.validation-info {
display: none;
}
.italicSmall {
font-style: normal;
font-size: 14px;
}
span {
line-height: 24px;
}
a {
color: #1574A1;
text-decoration: none;
}
.form-group {
display: flex;
flex-direction: column;
}
.headerlinecontainer {
.headerline1 {
font-size: 20px;
line-height: 32px;
font-weight: 400;
margin: 24px 0;
}
}
input,
select {
margin-top: 4px;
}
input {
border-radius: 8px;
border: 1px solid #8E9292;
padding: 16px;
&:focus,
&:focus-visible {
box-shadow: 0 0 0 2.5px #1574a1;
border: none;
outline: none;
}
&:disabled,
&.disabled {
background-color: #f5f5f5;
filter: grayscale(100%);
&:hover {
box-shadow: 0 0 0 4px transparent;
border: 1px solid #8e9292;
}
}
&:hover {
border: 1px solid #8e9292;
box-shadow: 0 0 0 4px #9fcee6;
}
}
select {
border-radius: 8px;
border: 1px solid #8E9292;
padding: 16px;
width: 100%;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%231474a2'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: 95% 50%;
background-size: 15px 9px;
appearance: none;
}
.creditCardSpecific {
div {
padding: 8px 0;
.headerlinecontainer {
padding: 0;
}
label {
font-weight: 500;
}
&:first-child {
display: none;
}
}
#infoRow2 {
margin-bottom: 0;
}
}
#cardExpirationContainer {
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 0;
@media (min-width: 400px) {
flex-direction: row;
.card_expirationYear,
.card_expirationMonth {
display: flex;
flex-direction: column;
width: 100%;
}
.card_expirationYear {
margin-left: 16px;
}
}
}
.optional-label {
span {
display: none;
}
&:after {
content: " (optional)";
font-weight: 300;
font-style: normal;
font-size: 15px;
}
}
.buttonContainer {
height: 48px;
button {
position: relative;
background: linear-gradient(270deg, #1574a1 0%, #003d58 100%);
border: none;
border-radius: 8px;
color: #fff;
justify-content: center;
font-weight: 500;
font-size: 16px;
height: 48px;
@media (hover: hover) {
background: linear-gradient(270deg, #1574a1 0%, #003d58 100%);
}
&:focus {
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
}
&:focus, // Mouse, touch, stylus focus
&:focus-visible {
// Keyboard focus for accessibility
outline: none;
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
color: #fff;
background: linear-gradient(270deg, rgba(6, 87, 124, 1) 0%, rgba(6, 87, 124, 1) 100%);
}
&:disabled {
background: #e3e4e4 !important;
background: linear-gradient(270deg, #e3e4e4 0%, #e3e4e4 100%) !important;
color: #4d5151 !important;
font-weight: 400;
height: 48px;
border: none;
border-radius: 8px;
cursor: pointer;
pointer-events: all;
}
&.has-loader {
color: #fff;
background: #06577c;
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
pointer-events: auto;
}
&.delay {
// fixes flicker while transitioning between states
transition: background 0s 0s ease-in-out;
}
}
}
//Error Styling
.has-error {
input,
select {
border: 1px solid #d4281c;
&:focus {
border: 1px solid transparent;
}
&:hover {
border: 1px solid #d4281c;
box-shadow: 0 0 0 4px #fcbfbb;
}
}
select {
border: 1px solid #d4281c;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%23d4281c'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: 95% 50%;
background-size: 15px 9px;
appearance: none;
}
small {
margin-top: 4px;
color: #d4281c;
}
}
.cc-error-container {
#alert-message {
background-color: #fcbfbb;
border: 1px solid #d4281c;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -21,6 +21,10 @@ const applicationConfig = {
"//" +
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 };

View file

@ -93,6 +93,7 @@ const storeActions = {
SAVE_PIA_ERROR_CODE: "savePiaErrorCode",
SAVE_PIA_WORK_ORDER: "savePiaWorkOrder",
SAVE_CCTOKEN: "saveCCToken",
SAVE_PAYPAL_TOKEN: "savePaypalToken",
};
export { storeActions };

View file

@ -4,6 +4,7 @@ const storeMutations = {
UPDATE_PIA_ERROR_CODE: "updatePiaErrorCode",
UPDATE_PIA_WORK_ORDER: "updatePiaWorkOrder",
UPDATE_CCTOKEN: "updateCCToken",
UPDATE_PAYPAL_TOKEN: "updatePaypalToken",
// VEHICLE MUTATIONS
UPDATE_YEAR: "updateYear",

View file

@ -52,7 +52,7 @@ https://safelite.atlassian.net/wiki/spaces/DC/pages/76644418/Button+Question+Com
:isWide="isWide"
:validationRules="validationRules"
:textPosition="textPosition"
:additionalButtonData="additionalButtonData"
:additionalButtonData="answer.additionalButtonData"
:lastValuePushedToGa="lastValuePushedToGa"
:setLastValuePushedToGa="setLastValuePushedToGa"
:suppressError="suppressError"
@ -252,6 +252,7 @@ export default {
buttonFooterCopy: answer.buttonFooterCopy ?? answer.buttonFooterCopy,
buttonImage: answer.buttonImage ?? answer.AnswerImageUrl,
buttonImageId: answer.buttonImageId ?? answer.ImageId,
additionalButtonData: answer.additionalButtonData ?? answer.additionalButtonData,
groupName: this.formatString(this.groupName),
value:
answer.value ??

View file

@ -8,7 +8,7 @@
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6">
<div class="col-md-6 col-xl-4">
<vehicleBanner
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />

View file

@ -10,8 +10,33 @@ export const promoPartNumberStrings = {
GLASS_CLEANER_DISCOUNT_PART_NUMBER: "DISC GLASS CLN",
};
export const addableVapsPromoPartNumbers = [
promoPartNumberStrings.RAIN_DEFENSE_DISCOUNT_PART_NUMBER,
promoPartNumberStrings.WIPER_DISCOUNT_PART_NUMBER,
];
export const pagesToStripPromoQueryStringFrom = ["quote", "payment-method"];
export function getPromosWithAddableVaps(promoList) {
if (!promoList.length) {
return [];
}
return promoList.filter((promo) => addableVapsPromoPartNumbers.includes(promo.partNumber));
}
export function getLineItemsThatMatchPromos(promos, availableLineItems) {
const matchingLineItems = [];
promos.forEach((promo) => {
promo.discountedLineItemIds.forEach((discountedLineItemId) => {
matchingLineItems.push(
...availableLineItems.filter((lineItem) => lineItem.id === discountedLineItemId)
);
});
});
return matchingLineItems;
}
export function getAddableVapsFromAvailableLineItems(availableLineItems) {
const addableVaps = [];
addableVaps.push(...findLineItemsWithPartType(partTypeStrings.FRONT_WIPER, availableLineItems));

View file

@ -1,167 +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 } from "@/helpers/date-helper";
import { getCalendarFile, download } from "@/helpers/add-to-calendar-helper";
export default {
name: "add-to-calendar",
components: {
calendarModalQuestion,
},
props: {
appointment: Object,
},
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;
},
},
},
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();
},
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`),
};
},
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>

View file

@ -1,5 +1,5 @@
// Components
import addToCalendar from "@/layouts/add-to-calendar/add-to-calendar.vue";
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";
@ -176,11 +176,20 @@ describe("Add-to-calendar methods...", () => {
});
});
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 };

View 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>

View file

@ -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 () => {

View file

@ -20,7 +20,19 @@
<p>{{ ScheduleDateFormatted }}</p>
<p>{{ ScheduleTimeFormatted }}</p>
</div>
<addToCalendar :appointment="this.AppointmentData" />
<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="AppointmentWordingText"></div>
</div>
@ -43,22 +55,17 @@
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, RouteCodeFlags } from "@/constants/schedule-constants";
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 {
calculateDuration,
combineDateAndTime,
shortTimeString,
addMinutes,
} from "@/helpers/date-helper";
import { Form } from "vee-validate";
import store from "@/store";
import { serviceType } from "@/constants/service-type";
import { get12HourTimeFormat, get12HourTimeMobileFormat } from "@/helpers/date-helper";
export default {
name: "confirmation",
@ -190,138 +197,6 @@ export default {
return `at ${get12HourTimeFormat(this.ScheduleStartTime)}`;
}
},
IsSameDayDropOff() {
const todaysDate = new Date().toISOString().split("T")[0];
return this.ScheduleDate === todaysDate;
},
Mobile_Appointment_Subject() {
return this.getCmsContent("Mobile_Appointment_Widget", "HeaderText");
},
Mobile_Appointment_Body() {
return this.getCmsContent("Mobile_Appointment_Widget", "BodyText");
},
InShop_Appointment_Subject() {
return this.getCmsContent("InShop_Appointment_Widget", "HeaderText");
},
InShop_Appointment_Body() {
return this.getCmsContent("InShop_Appointment_Widget", "BodyText");
},
Drop_Off_Appointment_Subject() {
return this.getCmsContent("Drop_Off_Appointment_Widget", "HeaderText");
},
Drop_Off_Appointment_Body() {
return this.getCmsContent("Drop_Off_Appointment_Widget", "BodyText");
},
Overnight_Drop_Off_Appointment_Subject() {
return this.getCmsContent("Drop_Off_Appointment_Widget", "HeaderText");
},
Overnight_Drop_Off_Appointment_Body() {
return this.getCmsContent("Drop_Off_Appointment_Widget", "BodyText");
},
All_Day_Drop_Off_Appointment_Subject() {
return this.getCmsContent("All_Day_Drop_Off_Appointment_Widget", "HeaderText");
},
All_Day_Drop_Off_Appointment_Body() {
return this.getCmsContent("All_Day_Drop_Off_Appointment_Widget", "BodyText");
},
Same_Day_Drop_Off_Appointment_Subject() {
return this.getCmsContent("Same_Day_Drop_Off_Appointment_Widget", "HeaderText");
},
Same_Day_Drop_Off_Appointment_Body() {
return this.getCmsContent("Same_Day_Drop_Off_Appointment_Widget", "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;
},
AppointmentData() {
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.Mobile_Appointment_Subject?.replace(
"{custom:SERVICETYPE}",
this.ServiceType
);
body = this.Mobile_Appointment_Body;
} else {
location = this.ProviderFullAddress?.replace("<br/>", "");
if (this.AppointmentType === AppointmentTypeStrings.DROP_OFF) {
if (this.RouteCode.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
subject = this.Overnight_Drop_Off_Appointment_Subject;
body = this.Overnight_Drop_Off_Appointment_Body;
} else if (this.RouteCode.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
if (this.IsSameDayDropOff) {
subject = this.Same_Day_Drop_Off_Appointment_Subject;
endDateTime = addMinutes(startDateTime, 120);
body = this.Same_Day_Drop_Off_Appointment_Body;
} else {
endDateTime = addMinutes(startDateTime, 120);
subject = this.All_Day_Drop_Off_Appointment_Subject?.replace(
"{custom:STARTDATETIME}",
shortTimeString(startDateTime)
).replace("{custom:ENDDATETIME}", shortTimeString(endDateTime));
body = this.All_Day_Drop_Off_Appointment_Body?.replace(
"{custom:ENDDATETIME}",
shortTimeString(endDateTime)
);
}
} //normal time slot
else {
subject = this.Drop_Off_Appointment_Subject?.replace(
"{custom:SERVICETYPE}",
this.ServiceType
);
body = this.Drop_Off_Appointment_Body?.replace(
"{custom:ADDRESS}",
location
);
}
} else {
subject = this.InShop_Appointment_Subject?.replace(
"{custom:SERVICETYPE}",
this.ServiceType
);
body = this.InShop_Appointment_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,
};
},
},
data() {
return {
workOrderNumber: store.getters.order.workOrderNumber,
};
},
methods: {
arePagePrerequisitesValid() {

View file

@ -194,7 +194,6 @@ export default {
taxedLineItems: [], // temporary, for use by cart.vue
availableLineItems: [],
paymentMethodInternalModel: this.getPaymentMethodFromStore(),
displayPiaAlert: this.getPiaAlert(),
};
},
methods: {
@ -250,9 +249,6 @@ export default {
packageReqs && serviceLocationReqs && isInsuranceSet && scheduleReqs && customerReqs
);
},
getPiaAlert() {
return store.getters.order.payment.piaErrorCode ? true : false;
},
getPaymentMethodFromStore() {
const isPia = store.getters.order.payment.isPia;
@ -283,24 +279,29 @@ 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.setupPia(this.paymentMethod);
break;
case paymentMethods.PAYPAL:
this.piaSetup("pp");
this.setupPia(this.paymentMethod);
break;
case paymentMethods.AFTERPAY:
this.piaSetup("ap");
this.setupPia(this.paymentMethod);
break;
default:
this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, null, false);
this.dispatchStoreAction(this.storeActions.SAVE_WORK_ORDER_FLAG, true, false);
this.dispatchStoreAction(
storeActions.SAVE_PAYMENT_METHOD_CHOICE,
this.paymentMethod,
false
);
await submitWorkOrder({ pageNameToLog: "payment-method" });
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_FORWARD,
@ -308,16 +309,11 @@ export default {
);
}
},
async piaSetup(payNowType) {
async setupPia(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_PAYMENT_METHOD_CHOICE,
payNowType,
false
);
// make sure pia error codes are reset
this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, null, false);
@ -357,11 +353,8 @@ export default {
isPiaDisabled() {
const piaExperience =
this.getSettingValue(experimentSettings.PIA_EXPERIENCE) === "PIA Optional";
const enablePIA =
this.getSettingValue(experimentSettings.SUBMIT_ORDER_ENABLE_PIA) === "true";
return false;
//return !(piaExperience && enablePIA);
return !piaExperience;
},
paymentMethod() {
if (this.isPiaDisabled) {
@ -370,6 +363,9 @@ export default {
return this.paymentMethodInternalModel;
},
displayPiaAlert() {
return store.getters.order.payment.piaErrorCode ? true : false;
},
},
watch: {
customCtaCopy(newValue) {

View file

@ -13,18 +13,13 @@ 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();
// from paypal
const piaError = getQuerystringParameter(queryStrings.ERROR);
const token = getQuerystringParameter(queryStrings.TOKEN);
const payerId = getQuerystringParameter(queryStrings.PAYERID);
// from credit card
const subscriptionID = getQuerystringParameter(queryStrings.SUBSCRIPTIONID);
if (piaError) {
console.log("Error during payment: " + piaError);
@ -35,25 +30,26 @@ export default {
);
this.$router.navigateWithoutSaving(this.navigationScenarios.PIA_ERROR, this.$route);
} else {
if (token && payerId) {
await this.processPaypalResponse();
} else {
if (subscriptionID) {
await this.processCreditCardResponse(subscriptionID);
} else {
console.log(
"Error during payment: " + token + " : " + payerId + " : " + subscriptionID
);
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,
piaError,
msg,
false
);
this.$router.navigateWithoutSaving(
this.navigationScenarios.PIA_ERROR,
this.$route
);
}
}
}
},
@ -62,12 +58,16 @@ export default {
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(subscriptionID) {
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);

View file

@ -1,7 +1,7 @@
<template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
<Form>
<loadingModal notFullScreen ref="loadingModal" />
<div class="container-fluid page-container-grouped-styles">
<div class="container-fluid">
<div class="row justify-content-center">
<div class="col-md-6">
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
@ -9,7 +9,7 @@
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<div class="col-md-6 col-xl-4 p-0">
<div id="card-container">
<iframe
ref="paymentFrame"
@ -20,15 +20,12 @@
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>
<navbar
cmsWidgetName="FunnelFooterWidget"
isForwardActionDisabled="true"
:isBackButtonHidden="shouldHideBackButton"
isSubmitHidden="true"
@back-clicked="backButtonAction" />
</div>
</div>
</div>
@ -55,16 +52,15 @@
<input type="hidden" name="sgNoKeystrokeProcessing" value="false" />
<input type="hidden" name="maskCharacter" value="*" />
<input type="hidden" name="styleSheetCode" :value="dynamicCSSUrl" />
<input type="hidden" name="styleSheetCode2" :value="dynamicHopCSSUrl" />
<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="sgheaderline1" :value="getHeaderLine1" />
<input type="hidden" name="sgHeader1subtitle" value="" />
<input type="hidden" name="sgheaderline2" :value="sgHeaderline2" />
<input type="hidden" name="sgheaderline2" :value="getHeaderLine2" />
<input type="hidden" name="sgheaderline3" value="" />
<input type="hidden" name="sgheaderline4" value="" />
<input type="hidden" name="sgheaderline5" value="*Required information" />
@ -168,10 +164,10 @@ 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
import { Form } from "vee-validate";
export default {
name: "payment",
@ -201,14 +197,19 @@ export default {
totalAmount: this.getAmountDue(),
displayAmount: this.getDisplayAmountDue(),
piaLineItems: this.getPiaLineItems(),
sgHeaderline1: this.getHeaderLine1(),
sgHeaderline2: this.getHeaderLine2(),
paypalPayment: this.isPaypal(),
};
},
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const signaturePromise = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_SIGNATURE,
null,
"payment"
);
// TODO: Data fetching Promise declarations
const promiseResultMap = [
@ -216,6 +217,10 @@ export default {
resultKey: "cmsContent",
promise: cmsContentPromise,
},
{
resultKey: "signature",
promise: signaturePromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
@ -223,18 +228,26 @@ export default {
// Call the "next" function to complete the transition to this page.
next(async (vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.fetchSignatureInfo();
vm.fetchSignatureInfo(resultMap.signature);
});
},
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`;
},
getHeaderLine1() {
if (!this.isPaypal()) {
return this.getCmsContent("PIAHeaderLine1", "Text");
}
return "";
},
getHeaderLine2() {
if (!this.isPaypal()) {
return this.getCmsContent("PIAHeaderLine2", "Text");
}
return "";
},
},
methods: {
arePagePrerequisitesValid() {
@ -243,7 +256,7 @@ export default {
},
getWOrkOrderNumber() {
if (store.getters.order.workOrderNumber) {
var items = store.getters.order.workOrderNumber.split("-");
const items = store.getters.order.workOrderNumber.split("-");
return items[1];
}
@ -286,41 +299,36 @@ export default {
: store.getters.order.serviceLocation.provider.address.zipCode;
},
getPaymentType() {
return store.getters.order.payment.piaType;
},
getHeaderLine1() {
if (!this.isPaypal()) {
return "Submit payment information";
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;
}
return "";
},
getHeaderLine2() {
if (!this.isPaypal()) {
return "Save time when you pay for your service today! Please select a payment option.";
}
return "";
},
getPiaLineItems() {
const itemsClone = { ...store.getters.order.lineItems };
delete itemsClone.serverData;
let itemsForPia = "";
const glassParts = store.getters.order.lineItems.glassParts;
const supportingItems = store.getters.order.lineItems.supportingItems;
const vaps = store.getters.order.lineItems.vaps;
const items = [...(glassParts ?? []), ...(supportingItems ?? []), ...(vaps ?? [])];
var itemsForPia = "";
for (var propertyName in itemsClone) {
for (var item in itemsClone[propertyName]) {
var amount =
+itemsClone[propertyName][item].laborAmount +
+itemsClone[propertyName][item].sellingPrice;
itemsForPia += itemsClone[propertyName][item].partType + "|" + amount + "|1|";
}
for (let i = 0; i < items.length; i++) {
const amount = items[i].laborAmount + items[i].sellingPrice;
itemsForPia += items[i].partType + "|" + amount + "|1|";
}
return itemsForPia;
},
getAmountDue() {
return 350.0;
return 1600;
//return baseMixin.methods.getAmountDue(store.getters.order.lineItems);
},
getDisplayAmountDue() {
return "$350.00";
return "$1600.00";
//return baseMixin.methods.getDisplayAmountDue(store.getters.order.lineItems);
},
isPaypal() {
@ -330,17 +338,25 @@ export default {
return false;
},
backButtonAction() {
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
// The only way I could figure out how to get navigation to work on a page with the iframe.
// This is also how a cancel from Paypal would work. Just a redirect to the payment-method page.
// The normal navigation sort of works but the page does not render. The "next" callback function does not get
// invoked in payment-method's beforeroute enter. I also spotted an error in vue-router.esm-bundler.js when this
// happens but was unable to ignore it with a try catch. The navigation guard error is:
// Error: Redirected from "/?fmgPage=payment-method" to "{ params: {isSavingNation: "false"}, query: "fmgPage: "payment-method"}"
// I don't know if the navigation guard thinks the to and from are the same page.
// Also tried overriding with a $router.push
window.location = applicationConfig.PIA_CANCEL_URL;
//this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
},
forwardButtonAction() {},
async fetchSignatureInfo() {
async fetchSignatureInfo(signatureInfo) {
if (this.isPaypal()) {
this.$refs.loadingModal.showModal();
}
const signatureInfo = await this.dispatchStoreAction(storeActions.GET_SIGNATURE);
this.authToken = signatureInfo.data.token;
this.authSignature = signatureInfo.data.signature;
this.authSignatureStart = signatureInfo.data.startDate;
this.authToken = signatureInfo.token;
this.authSignature = signatureInfo.signature;
this.authSignatureStart = signatureInfo.startDate;
this.$nextTick(() => {
this.$refs.myForm.submit();
@ -361,7 +377,6 @@ export default {
components: {
funnelHeader,
navbar,
Form,
loadingModal,
},
};
@ -370,7 +385,7 @@ export default {
<style lang="scss">
.hop-iframe {
position: relative;
min-height: 1350px;
min-height: 1320px;
width: 100%;
border: 0 none;
}

View file

@ -5,7 +5,6 @@
:groupName="groupName"
buttonTypeString="listButtonHorizontal"
v-model="selectedValues"
:additionalButtonData="additionalButtonData"
isRequired />
</div>
</template>
@ -25,7 +24,13 @@ export default {
},
computed: {
answersFromCms() {
return this.getCmsContent(this.cmsWidgetName, "Answers");
const answers = this.getCmsContent(this.cmsWidgetName, "Answers");
if (answers) {
answers.forEach(
(answer) => (answer.additionalButtonData = this.additionalButtonData)
);
}
return answers;
},
additionalButtonData() {
return {

View file

@ -265,7 +265,7 @@ export default {
}
this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.selectedVaps, false);
this.dispatchStoreAction(this.storeActions.SAVE_PROMOS, this.newValidatedPromos, false);
this.dispatchStoreAction(this.storeActions.SAVE_PROMOS, this.allActivePromos, false);
const payment = this.$store.getters.payment;
if (payment.isInsurance) {

View file

@ -46,6 +46,7 @@ describe("service-package-question.vue", () => {
price: 35.5,
},
],
activePromos: [],
};
const packageNameKey = "05_01_CSR_Quote_Standard_Repair";
Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]);
@ -291,6 +292,7 @@ describe("service-package-question.vue, matching business rules for package disp
price: 35.5,
},
],
activePromos: [],
};
});
it("should match the 05_01_CSR_Quote_Standard_Repair mock", () => {

View file

@ -32,6 +32,8 @@ import {
import {
getPromosThatMatchLineItemsOnOrder,
removeVapsPromosFromPromoArray,
getPromosWithAddableVaps,
getLineItemsThatMatchPromos,
} from "@/helpers/promotions-helper";
export default {
@ -54,7 +56,10 @@ export default {
},
watch: {
availableLineItems() {
if (this.$store.getters.payment.isInsurance != null) {
if (
this.$store.getters.payment.isInsurance != null ||
getPromosWithAddableVaps(this.activePromos).length
) {
this.selectDefaultPackage();
}
},
@ -217,13 +222,22 @@ export default {
return price;
},
selectDefaultPackage() {
const promosWithAddableVaps = getPromosWithAddableVaps(this.activePromos);
const vapsThatMatchPromos = getLineItemsThatMatchPromos(
promosWithAddableVaps,
this.nullSafeAvailableLineItems
);
const vapsFromStore = this.$store.getters.lineItems.vaps ?? [];
const vapsToUseForDefaultPackageSelection = this.combineLineItemsWithoutDuplicates(
vapsThatMatchPromos,
vapsFromStore
);
this.selectedPackageName = getHighestRequiredTier(
this.glassToReplace,
this.nullSafeAvailableLineItems,
this.isRepair,
vapsFromStore
vapsToUseForDefaultPackageSelection
);
},
getVapsLineItemsForSelectedPackage(packageName) {
@ -244,6 +258,17 @@ export default {
return vapsLineItemsForSelectedPackage;
},
combineLineItemsWithoutDuplicates(lineItemsOne, lineItemsTwo) {
const combinedLineItemArray = [...lineItemsTwo];
lineItemsOne.forEach((lineItemOne) => {
if (
!lineItemsTwo.filter((lineItemTwo) => lineItemTwo.id === lineItemOne.id).length
) {
combinedLineItemArray.push(lineItemOne);
}
});
return combinedLineItemArray;
},
getCustomValueFromString(str) {
switch (str) {
case "isRecalibrationOnOrder":

View file

@ -122,6 +122,8 @@ const mockProps = {
buttonBodyCopy:
"<ul><li>buttonBodyCopy test copy</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>",
buttonFooterCopy: "buttonFooterCopy test copy",
buttonAuxillaryCopy: "buttonAuxillaryCopy test copy",
additionalButtonData: { strikeThroughPrice: "strikeThroughPrice test copy" },
};
function setupMocks({ mountOptionsMockData = {} }) {

View file

@ -7,7 +7,13 @@
<div class="package-specs">
<p class="m-0">
<span v-html="this.buttonLabel"></span>
<span class="pricing-info" v-html="this.buttonAuxillaryCopy"></span>
<span class="pricing-info">
<span
class="strikethrough-price"
v-if="shouldDisplayStrikeThroughPrice"
v-html="this.additionalButtonData.strikeThroughPrice"></span>
<span v-html="this.buttonAuxillaryCopy"></span>
</span>
</p>
<p
class="sub-label m-0"
@ -76,6 +82,12 @@ export default {
arrayOfListItemsFromBodyText() {
return this.getArrayOfListItemsFromRawCmsCopy(this.buttonBodyCopy);
},
shouldDisplayStrikeThroughPrice() {
const displayPrice = this.buttonAuxillaryCopy.substring(
this.buttonAuxillaryCopy.indexOf("$")
);
return displayPrice != this.additionalButtonData.strikeThroughPrice;
},
},
methods: {
getRouterLinkDisplayTextFromCopy,
@ -219,6 +231,13 @@ export default {
&.pricing-info {
color: $green;
font-size: 0.875rem;
span.strikethrough-price {
text-decoration: line-through;
font-size: 0.75rem;
font-weight: 400;
color: $gray-550;
padding-right: 8px;
}
}
}
&.sub-label {

View file

@ -9,7 +9,7 @@
</div>
<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"

View file

@ -9,7 +9,7 @@
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-lg-4">
<div class="col-md-6 col-xl-4">
<vehicleBanner
ref="vehicleBanner"
cmsWidgetName="VehicleBannerWidget"

View file

@ -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 };
}

View file

@ -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>

View file

@ -124,16 +124,17 @@ export default {
},
getAmountDue(lineItems) {
// TODO: verify tax is included in lineitems
// Price everything in lineitems except the server data string
var amountDue = 0;
const itemsClone = { ...lineItems };
delete itemsClone.serverData;
for (var propertyName in itemsClone) {
if (itemsClone[propertyName]) {
amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(
itemsClone[propertyName]
);
}
if (lineItems.glassParts) {
amountDue = this.getTotalPriceOfAllLineItemsAndChildParts(lineItems.glassParts);
}
if (lineItems.supportingItems) {
amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(
lineItems.supportingItems
);
}
if (lineItems.supportingItems.vaps) {
amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(lineItems.vaps);
}
return amountDue;
},

View file

@ -99,6 +99,7 @@ const getDefaultState = () => {
piaType: null,
piaErrorCode: null,
inactivePromos: null,
paypalToken: null,
ccToken: {
subscriptionId: null,
expMonth: null,
@ -270,6 +271,9 @@ export const mutations = {
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;
},
@ -495,10 +499,12 @@ export const mutations = {
state.order.lineItems.glassParts = sessionInformation.order.lineItems.glassParts;
state.order.lineItems.supportingItems = sessionInformation.order.lineItems.supportingItems;
state.order.lineItems.vaps = sessionInformation.order.lineItems.vaps;
state.order.lineItems.promos = sessionInformation.order.lineItems.promos;
state.order.lineItems.serverData = sessionInformation.order.lineItems.serverData;
state.order.payment.parentAccountNumber =
sessionInformation.order.payment.parentAccountNumber;
state.order.payment.inactivePromos = sessionInformation.order.payment.inactivePromos;
state.order.serviceLocation.address =
sessionInformation.order.serviceLocation.streetAddress;
@ -691,6 +697,7 @@ export const getters = {
},
experimentSettings: (state) =>
state.applicationUser.experiments
.filter((x) => !!x.isActive)
.map((x) => x.settings)
.reduce((r, c) => Object.assign(r, c), {}) ?? {},
};
@ -1644,6 +1651,11 @@ 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,
@ -1777,6 +1789,9 @@ export const actions = {
saveCCToken(context, ccToken) {
context.commit(storeMutations.UPDATE_CCTOKEN, ccToken);
},
savePaypalToken(context, ppToken) {
context.commit(storeMutations.UPDATE_PAYPAL_TOKEN, ppToken);
},
// Business domain actions
@ -2229,6 +2244,7 @@ export const actions = {
addGuidToLineItemsIfNotAlreadyThere(addableVaps);
const requestObject = {
promoCode: promoCode,
addableVaps: addableVaps,
order: {
appointmentType: order.serviceLocation.appointmentType,
carId: order.vehicle.carId,
@ -2237,7 +2253,6 @@ export const actions = {
isRepair: order.damage.isRepair,
glassToReplace: renameGlassToReplaceAttributes(order.damage.glassToReplace),
lineItemsOnOrder: getArrayOfAllLineItems(order.lineItems),
addableVaps: addableVaps,
parentAccountNumber: order.payment.parentAccountNumber,
referralSequenceNumber: order.referralSequenceNumber,
serviceState: order.serviceLocation.state,
@ -2258,10 +2273,12 @@ export const actions = {
pageNameToLog: pageNameToLog,
});
context.commit(
storeMutations.UPDATE_LINE_ITEMS_SERVER_DATA,
validateResponse.data.lineItemsServerData
);
if (validateResponse?.data?.serverData) {
context.commit(
storeMutations.UPDATE_LINE_ITEMS_SERVER_DATA,
validateResponse.data.serverData
);
}
return validateResponse;
},
@ -2516,8 +2533,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
);
@ -2533,7 +2549,7 @@ function addPricesToLineItems(lineItems, pricingLineItems) {
lineItem.salesTax = pricedLineItem.salesTax;
});
return pricedLineItems;
return lineItems;
}
function addTaxesToPricedLineItems(lineItems, taxingLineItems) {

View file

@ -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' : '',