DigitalConsumer.FixMyGlass/src/layouts/quote/afterpay-modal-banner/afterpay-modal-banner.vue
scottkiener-at-safelite ef3f7ca55b Formatting
2026-07-13 07:58:40 -04:00

315 lines
9.2 KiB
Vue

<template>
<div class="afterpay-modal-banner" role="alert">
<div>
<span
class="callout"
v-for="token in headerCopyTokens"
:key="token"
v-html="token"></span>
</div>
<div>
<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 v-html="token"></span>
</span>
<span class="nbsp">&ensp;</span>
<textLink
linkType="text"
:text="bannerCta"
href="javascript:void(0)"
@click-event="openModal" />
</div>
</div>
<modal
:ref="modalName"
class="afterpay-modal"
:headerText="modalHeaderText"
:isFooterButtonSuppressed="true">
<template v-slot:modal-header-slot>
<span>{{ modalSubHeaderText }}</span>
</template>
<template v-slot>
<span class="afterpay-sections">
<span
v-for="section in afterpaySectionsContent"
:key="section"
class="afterpay-section">
<span class="section-header">
<img v-if="section.AnswerImageUrl" :src="section.AnswerImageUrl" />
<h3 v-if="section.Text" v-html="section.Text"></h3>
</span>
<span v-html="section.content"></span>
</span>
</span>
</template>
<template v-slot:modal-footer-slot>
<p class="modal-disclaimer" v-html="modalDisclaimerText"></p>
</template>
</modal>
</template>
<script>
import {
doesCopyContainTextLink,
splitCopyOnCMSPlaceHolder,
getRouterLinkRouteFromCopy,
getRouterLinkDisplayTextFromCopy,
getExternalLink,
splitCMSCopyOnBR,
} from "@/helpers/cms-content-helper";
import textLink from "@/ux-components/text-link/text-link";
import modal from "@/digital-components/modal/modal";
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-banner",
props: {
cmsWidgetName: String,
modalWidgetName: String,
},
data() {
return {};
},
methods: {
isInlineImageToken,
isAfterpayPriceToken,
getInlineAltText,
doesCopyContainTextLink,
splitCopyOnCMSPlaceHolder,
getRouterLinkRouteFromCopy,
getRouterLinkDisplayTextFromCopy,
getExternalLink,
openModal() {
this.modal?.openModal();
},
closeModal() {
this.modal?.closeModal();
},
},
computed: {
imageUrl() {
return this.getCmsContent(this.cmsWidgetName, "Image");
},
hasImage() {
return !!this.imageUrl;
},
headerCopyTokens() {
return splitCMSCopyOnBR(this.getCmsContent(this.cmsWidgetName, "HeaderText"));
},
afterpayCopyTokens() {
return splitCopyOnCMSPlaceHolder(this.getCmsContent(this.cmsWidgetName, "BodyText"));
},
bannerCta() {
return this.getCmsContent(this.cmsWidgetName, "FooterText");
},
modalHeaderText() {
return this.getCmsContent(this.modalWidgetName, "HeaderText");
},
modalDisclaimerText() {
return this.getCmsContent(this.modalWidgetName, "BodyText2");
},
modalName() {
return this.modalWidgetName;
},
modal() {
return this.$refs[this.modalName];
},
afterpaySectionsContent() {
const sections = this.getCmsContent("AfterpayModalSectionsWidget", "Answers");
if (!sections) return [];
sections.forEach((section) => {
const sectionContent = this.getCmsContent(section.Name, "BodyText");
section["content"] = sectionContent;
});
return sections;
},
},
components: {
textLink,
modal,
},
};
</script>
<style lang="scss" scoped>
.afterpay-modal-banner {
display: flex;
flex-direction: column;
background-color: $blue-150;
justify-content: center;
align-items: center;
padding: 1rem;
border-radius: 1rem;
@include media-breakpoint-up(md) {
flex-direction: row;
}
.callout {
font-family: UrbanistSemibold, Arial, Helvetica, sans-serif;
white-space: nowrap;
}
> div:first-of-type {
display: flex;
color: $black;
font-size: $font-size-20;
line-height: 2rem;
@include media-breakpoint-down(md) {
border-bottom: 1px solid;
padding-bottom: 0.5rem;
width: 100%;
justify-content: center;
> span:first-of-type {
margin-right: 0.25rem;
}
}
@include media-breakpoint-up(md) {
flex-direction: column;
border-right: 1px solid;
padding-right: 1rem;
}
}
> div:last-of-type {
color: $gray-650;
text-align: left;
@include media-breakpoint-down(md) {
padding-top: 1rem;
}
@include media-breakpoint-up(md) {
padding-left: 1rem;
}
}
}
.afterpay-modal {
&.modal.modal-component {
:deep(.modal-dialog) {
margin: 0 1rem;
top: 0;
@include media-breakpoint-up(md) {
width: 100%;
max-width: 840px;
top: 3.4rem;
right: 0;
left: 0;
transform: none;
margin: 0 auto;
padding: 0 1rem;
}
.modal-content {
border-radius: 0.5rem;
margin: 1.5rem 0;
.modal-header.mt-6 {
background-color: $blue-100;
margin-top: 0;
padding: 1.5rem 2rem 1.5rem 1.5rem;
font-family: UrbanistSemibold;
.modal-title.justify-content-center {
// OVERRIDE
justify-content: flex-start;
}
.btn-close {
top: 1.25rem;
right: 1.5rem;
}
}
.afterpay-section {
display: block;
border: 1px solid $gray-200;
border-radius: 0.25rem;
padding: 1rem;
.section-header {
display: flex;
align-items: center;
margin-bottom: 1rem;
img {
width: 40px;
height: 40px;
}
h3 {
font-family: UrbanistBold;
font-size: 1rem;
margin: 0 0 0 0.5rem;
color: $black;
}
}
p {
font-size: 0.75rem;
}
strong {
font-family: UrbanistSemibold;
font-size: 0.875rem;
}
}
.modal-body {
padding: 1.5rem;
.afterpay-sections {
@include media-breakpoint-up(md) {
display: flex;
column-gap: 1rem;
align-items: stretch;
}
.afterpay-section {
margin-bottom: 1rem;
@include media-breakpoint-up(md) {
flex: 0 1 33%;
margin-bottom: 0;
}
&:last-of-type {
margin-bottom: 0;
}
}
}
}
}
&.modal-dialog-centered {
height: auto;
min-height: auto;
}
}
}
:deep(.modal-footer) {
position: relative;
background: $gray-100;
.modal-disclaimer {
font-style: italic;
font-size: 0.75rem;
a {
padding: 0;
line-height: 1.625;
}
}
}
}
</style>