Merge branch 'develop' into feature/CSR-1855
This commit is contained in:
commit
1d0b297f63
16 changed files with 233 additions and 18 deletions
|
|
@ -19,6 +19,7 @@ const queryStrings = {
|
|||
AUTH_CODE: "auth_code",
|
||||
TRANSACTION_ID: "transaction_id",
|
||||
TRANS_REFERENCE_NUMBER: "auth_trans_ref_no",
|
||||
LAST_FOUR: "last_four",
|
||||
DISPLAY_PIA_ALERT: "displayPiaAlert",
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -265,8 +265,8 @@ describe("Question Chain component", () => {
|
|||
],
|
||||
},
|
||||
],
|
||||
index: 0,
|
||||
});
|
||||
await wrapper.setProps({ index: 0 });
|
||||
|
||||
//Act
|
||||
const result = wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
|
||||
|
|
|
|||
|
|
@ -834,7 +834,7 @@ export default {
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem 1.5rem;
|
||||
&:nth-last-child(-n + 3) {
|
||||
&:nth-last-child(-n + 4) {
|
||||
background-color: $green-100;
|
||||
}
|
||||
&:last-child {
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@
|
|||
:isMetaValid="meta.valid"
|
||||
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
|
||||
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
|
||||
v-if="questionsData"
|
||||
:questionsData="questionsData"
|
||||
validationRules="questions-required"
|
||||
v-model="selectedAnswers"
|
||||
@forwardButtonAction="forwardButtonAction"
|
||||
@back-click="navigateBack"
|
||||
:key="currentGlassIndex"
|
||||
:index="currentGlassIndex" />
|
||||
</Form>
|
||||
</template>
|
||||
|
|
@ -55,7 +57,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
questionsData: [],
|
||||
questionsData: null,
|
||||
selectedAnswers: {},
|
||||
currentGlassIndex: 0,
|
||||
};
|
||||
|
|
@ -120,6 +122,12 @@ export default {
|
|||
|
||||
return updatedGlass;
|
||||
});
|
||||
|
||||
// if no preanswered questions then make sure index starts with the correct value
|
||||
this.currentGlassIndex = this.calculateQuestionIndex(
|
||||
this.currentGlassIndex,
|
||||
this.questionsData
|
||||
);
|
||||
},
|
||||
async forwardButtonAction() {
|
||||
const questionAnswersArray = this.questionsData.map((glass) => {
|
||||
|
|
|
|||
|
|
@ -145,6 +145,8 @@ export default {
|
|||
computed: {
|
||||
ShowCart() {
|
||||
if (this.isPia && store.getters.submittedOrder.settledTenderAmount == 0) {
|
||||
// settleTenderAmount always shows 0 via localhost or dev.
|
||||
// Temporarily set return true to see cart in localhost or dev environment
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@
|
|||
:isMetaValid="meta.valid"
|
||||
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
|
||||
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
|
||||
v-if="questionsData"
|
||||
:questionsData="questionsData"
|
||||
validationRules="questions-required"
|
||||
v-model="selectedAnswers"
|
||||
@forwardButtonAction="forwardButtonAction"
|
||||
@back-click="navigateBack"
|
||||
:key="currentGlassIndex"
|
||||
:index="currentGlassIndex" />
|
||||
</Form>
|
||||
</template>
|
||||
|
|
@ -55,7 +57,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
questionsData: [],
|
||||
questionsData: null,
|
||||
selectedAnswers: {},
|
||||
currentGlassIndex: 0,
|
||||
};
|
||||
|
|
@ -119,6 +121,12 @@ export default {
|
|||
|
||||
return updatedGlass;
|
||||
});
|
||||
|
||||
// if no preanswered questions then make sure index starts with the correct value
|
||||
this.currentGlassIndex = this.calculateQuestionIndex(
|
||||
this.currentGlassIndex,
|
||||
this.questionsData
|
||||
);
|
||||
},
|
||||
async forwardButtonAction() {
|
||||
const questionAnswersArray = this.questionsData.map((glass) => {
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@
|
|||
:isMetaValid="meta.valid"
|
||||
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
|
||||
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
|
||||
v-if="questionsData"
|
||||
:questionsData="questionsData"
|
||||
validationRules="questions-required"
|
||||
v-model="selectedAnswers"
|
||||
@forwardButtonAction="forwardButtonAction"
|
||||
@back-click="navigateBack"
|
||||
:key="currentGlassIndex"
|
||||
:index="currentGlassIndex" />
|
||||
</Form>
|
||||
</template>
|
||||
|
|
@ -55,7 +57,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
questionsData: [],
|
||||
questionsData: null,
|
||||
selectedAnswers: {},
|
||||
currentGlassIndex: 0,
|
||||
};
|
||||
|
|
@ -71,7 +73,7 @@ export default {
|
|||
return this.$store.getters.pageData(fmgPageValues.PART_QUESTIONS).partsOrQuestions;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
mounted() {
|
||||
this.getInitialQuestionData();
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -117,6 +119,12 @@ export default {
|
|||
|
||||
return updatedGlass;
|
||||
});
|
||||
|
||||
// if no preanswered questions then make sure index starts with the correct value
|
||||
this.currentGlassIndex = this.calculateQuestionIndex(
|
||||
this.currentGlassIndex,
|
||||
this.questionsData
|
||||
);
|
||||
},
|
||||
async forwardButtonAction() {
|
||||
const questionAnswersArray = this.questionsData.map((glass) => {
|
||||
|
|
|
|||
|
|
@ -15,10 +15,6 @@
|
|||
|
||||
<funnelSubHeader class="mb-5 mt-4" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<p v-if="isPiaDisabled" class="fw-normal no-pia-disclaimer">
|
||||
<span>{{ noPiaDisclaimer }}</span>
|
||||
</p>
|
||||
|
||||
<hr class="my-0" />
|
||||
|
||||
<reviewDropdown ref="reviewDropdown" />
|
||||
|
|
@ -56,6 +52,13 @@
|
|||
v-model="paymentMethodInternalModel"
|
||||
validationRules="option-required" />
|
||||
|
||||
<alert
|
||||
v-if="isPiaDisabled"
|
||||
:isDismissible="false"
|
||||
alertClass="alert-info"
|
||||
cmsWidgetName="NoPiaDisclaimerWidget"
|
||||
shouldScrollToOnMount="false" />
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
|
|
@ -307,6 +310,7 @@ export default {
|
|||
availableVaps: [],
|
||||
paymentMethodInternalModel: this.getPaymentMethodFromStore(),
|
||||
inactivePromos: this.getInactivePromosFromStore(),
|
||||
isPiaDisabled: this.getPiaStatusFromStore(),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -499,6 +503,13 @@ export default {
|
|||
this.$route
|
||||
);
|
||||
},
|
||||
getPiaStatusFromStore() {
|
||||
const piaExperience = this.getSettingValue(experimentSettings.PIA_EXPERIENCE);
|
||||
|
||||
const isEnabled = piaExperience === "PIA Optional" || piaExperience === "PIA Required";
|
||||
|
||||
return !isEnabled;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
damageInfo() {
|
||||
|
|
@ -519,13 +530,6 @@ export default {
|
|||
return null;
|
||||
}
|
||||
},
|
||||
isPiaDisabled() {
|
||||
const piaExperience = this.getSettingValue(experimentSettings.PIA_EXPERIENCE);
|
||||
|
||||
const isEnabled = piaExperience === "PIA Optional" || piaExperience === "PIA Required";
|
||||
|
||||
return !isEnabled;
|
||||
},
|
||||
paymentMethod() {
|
||||
if (this.isPiaDisabled) {
|
||||
return paymentMethods.LATER;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,9 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
validateAlphanumeric() {
|
||||
this.value = this.value.replace(ALPHANUMERIC_REGEX, "");
|
||||
setTimeout(() => {
|
||||
this.value = this.value.replace(ALPHANUMERIC_REGEX, "");
|
||||
});
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ export default {
|
|||
const transReferenceNumber = getQuerystringParameter(
|
||||
queryStrings.TRANS_REFERENCE_NUMBER
|
||||
);
|
||||
const lastFour = getQuerystringParameter(queryStrings.LAST_FOUR);
|
||||
|
||||
var ccToken = {
|
||||
subscriptionId: subscriptionID,
|
||||
|
|
@ -119,6 +120,7 @@ export default {
|
|||
authCode: authCode,
|
||||
transactionId: transactionId,
|
||||
transReferenceNumber: transReferenceNumber,
|
||||
lastFour: lastFour,
|
||||
};
|
||||
baseMixin.methods.dispatchStoreAction(storeActions.SAVE_CCTOKEN, ccToken, false);
|
||||
await this.saveAndSubmitWorkOrder();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
<template>
|
||||
<div class="alert fade show my-2 py-2 border-0 alert-info" role="alert">
|
||||
<component :is="'script'" src="https://js.afterpay.com/afterpay-1.x.js" async></component>
|
||||
|
||||
<div class="mx-4 my-0 fw-bold alert-heading">
|
||||
<span v-for="token in afterpayCopyTokens" :key="token">
|
||||
<span v-if="isAfterpayPriceToken(token)">{{ afterpayPrice }}</span>
|
||||
<span v-else-if="isInlineImageToken(token)">
|
||||
<img v-if="hasImage" :src="imageUrl" :alt="getInlineAltText(token)" />
|
||||
<span v-else>{{ getInlineAltText(token) }}</span>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ token }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="mx-4 my-0 fw-bold alert-heading">
|
||||
<a
|
||||
id="afterpay-learnmore"
|
||||
href="#"
|
||||
data-afterpay-modal="en_US-safelite"
|
||||
data-bind="click:afterpayLearnMore">
|
||||
{{ modalCopy }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { splitCopyOnCMSPlaceHolder } from "@/helpers/cms-content-helper";
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
|
||||
const INLINE_IMAGE_TOKEN = "custom:inlineImage";
|
||||
const AFTERPAY_PRICE_TOKEN = "custom:afterpayPrice";
|
||||
|
||||
function isInlineImageToken(token) {
|
||||
return token.includes(INLINE_IMAGE_TOKEN);
|
||||
}
|
||||
function isAfterpayPriceToken(token) {
|
||||
return token.includes(AFTERPAY_PRICE_TOKEN);
|
||||
}
|
||||
|
||||
function getInlineAltText(token) {
|
||||
const innerTokens = token.split(",");
|
||||
|
||||
return innerTokens[1] ?? "";
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "afterpay-modal-banner",
|
||||
props: {
|
||||
cmsWidgetName: String,
|
||||
lineItems: Array,
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
isInlineImageToken,
|
||||
isAfterpayPriceToken,
|
||||
getInlineAltText,
|
||||
},
|
||||
computed: {
|
||||
nullSafeLineItems() {
|
||||
return this.lineItems ?? [];
|
||||
},
|
||||
imageUrl() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "Image");
|
||||
},
|
||||
hasImage() {
|
||||
return !!this.imageUrl;
|
||||
},
|
||||
afterpayCopyTokens() {
|
||||
return splitCopyOnCMSPlaceHolder(this.getCmsContent(this.cmsWidgetName, "HeaderText"));
|
||||
},
|
||||
modalCopy() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "SubheaderText");
|
||||
},
|
||||
afterpayPrice() {
|
||||
const price = baseMixin.methods.getTierOnePackagePrice(
|
||||
baseMixin.methods.filterOutFees(this.nullSafeLineItems)
|
||||
);
|
||||
|
||||
const adjusted = (price / 4).toFixed(2);
|
||||
|
||||
return `$${adjusted}`;
|
||||
},
|
||||
},
|
||||
components: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.alert {
|
||||
padding: 0.675rem 0;
|
||||
&.alert-info {
|
||||
background-color: $blue-100;
|
||||
.alert-heading {
|
||||
color: $blue-700;
|
||||
}
|
||||
svg {
|
||||
fill: $blue-700;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
& .alert-heading {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -34,6 +34,11 @@
|
|||
validationRules="option-required"
|
||||
isRequired />
|
||||
|
||||
<afterpayModalBanner
|
||||
v-if="!isInsuranceSelected"
|
||||
cmsWidgetName="AfterpayModalWidget"
|
||||
:lineItems="availableLineItems" />
|
||||
|
||||
<textBlock
|
||||
cmsWidgetName="quoteDisclaimer"
|
||||
justifyText="left"
|
||||
|
|
@ -68,6 +73,7 @@ import servicePackageQuestion from "./service-package-question/service-package-q
|
|||
import textBlock from "@/digital-components/text-block/text-block";
|
||||
import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
import afterpayModalBanner from "@/layouts/quote/afterpay-modal-banner/afterpay-modal-banner";
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
import vehicleQuestionsMixin from "../../mixins/vehicle-questions-mixin";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
|
|
@ -315,6 +321,7 @@ export default {
|
|||
servicePackageQuestion,
|
||||
contentGroupModal,
|
||||
loadingModal,
|
||||
afterpayModalBanner,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -146,6 +146,13 @@ export default {
|
|||
|
||||
return glass;
|
||||
},
|
||||
calculateQuestionIndex(currentGlassIndex, questionsData) {
|
||||
// if no preanswered questions then make sure index starts with the correct value
|
||||
if (currentGlassIndex === 0) {
|
||||
return questionsData?.findIndex((glass) => glass.questions?.length > 0);
|
||||
}
|
||||
return currentGlassIndex;
|
||||
},
|
||||
handleCompletedQuestionChainAnswers(answer, glassKey, vm) {
|
||||
// only runs when all questions in a question-chain have been answered
|
||||
// when selectedAnswers updates, user has completed this part's question chain and has a final answer
|
||||
|
|
|
|||
|
|
@ -411,6 +411,57 @@ describe("vehicle-questions-mixin", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("calculateQuestionIndex", () => {
|
||||
describe("if currentGlassIndex is zero...", () => {
|
||||
test("should return index of first glass piece with questions", () => {
|
||||
// Arrange
|
||||
const questionsData = [
|
||||
{
|
||||
glassName: "Single",
|
||||
glassLocation: "Windshield",
|
||||
},
|
||||
{
|
||||
glassName: "Front",
|
||||
glassLocation: "Driver",
|
||||
questions: ["example question"],
|
||||
},
|
||||
];
|
||||
const glassIndex = 0;
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const returnedIndex = wrapper.vm.calculateQuestionIndex(glassIndex, questionsData);
|
||||
|
||||
// Assert
|
||||
expect(returnedIndex).toBe(1);
|
||||
});
|
||||
});
|
||||
describe("if currentGlassIndex is NOT zero...", () => {
|
||||
test("should return currentGlassIndex value", () => {
|
||||
// Arrange
|
||||
const questionsData = [
|
||||
{
|
||||
glassName: "Single",
|
||||
glassLocation: "Windshield",
|
||||
},
|
||||
{
|
||||
glassName: "Front",
|
||||
glassLocation: "Driver",
|
||||
questions: ["example question"],
|
||||
},
|
||||
];
|
||||
const glassIndex = 1;
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const returnedIndex = wrapper.vm.calculateQuestionIndex(glassIndex, questionsData);
|
||||
|
||||
// Assert
|
||||
expect(returnedIndex).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleCompletedQuestionChainAnswers", () => {
|
||||
describe("selectedAnswers", () => {
|
||||
test("should be cleared to be empty", () => {
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ const getDefaultState = () => {
|
|||
authCode: null,
|
||||
transactionId: null,
|
||||
transReferenceNumber: null,
|
||||
lastFour: null,
|
||||
},
|
||||
},
|
||||
schedule: {
|
||||
|
|
@ -274,6 +275,7 @@ export const mutations = {
|
|||
state.order.payment.ccToken.authCode = ccToken.authCode;
|
||||
state.order.payment.ccToken.transactionId = ccToken.transactionId;
|
||||
state.order.payment.ccToken.transReferenceNumber = ccToken.transReferenceNumber;
|
||||
state.order.payment.ccToken.lastFour = ccToken.lastFour;
|
||||
},
|
||||
updatePaypalToken(state, ppToken) {
|
||||
state.order.payment.paypalToken = ppToken;
|
||||
|
|
@ -1680,6 +1682,7 @@ export const actions = {
|
|||
authCode: order.payment.ccToken.authCode,
|
||||
transactionId: order.payment.ccToken.transactionId,
|
||||
transReferenceNumber: order.payment.ccToken.transReferenceNumber,
|
||||
lastFour: order.payment.ccToken.lastFour,
|
||||
},
|
||||
},
|
||||
serviceLocation: {
|
||||
|
|
|
|||
|
|
@ -742,6 +742,7 @@ describe("Actions", () => {
|
|||
authCode: null,
|
||||
transactionId: null,
|
||||
transReferenceNumber: null,
|
||||
lastFour: null,
|
||||
},
|
||||
},
|
||||
serviceLocation: {},
|
||||
|
|
|
|||
Loading…
Reference in a new issue